diff --git a/.env.test.sample b/.env.test.sample new file mode 100644 index 00000000..564f576f --- /dev/null +++ b/.env.test.sample @@ -0,0 +1,6 @@ +CYPRESS_MAILCHIMP_USERNAME="your mailchimp username" +CYPRESS_MAILCHIMP_PASSWORD="your mailchimp password" + +# Generate API Key: https://mailchimp.com/help/about-api-keys/ +CYPRESS_MAILCHIMP_API_KEY="generate an API key from your mailchimp account and paste here" # e.g. "1234567890abcdef1234567890abcdef-us19" +CYPRESS_MAILCHIMP_API_SERVER_PREFIX="" # e.g "us19" - your server prefix may be different \ No newline at end of file diff --git a/.gitignore b/.gitignore index 62369c7e..ba40ff31 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist/* docs-built .phpunit.result.cache /.wp-env.override.json +.env.test tests/cypress/videos tests/cypress/screenshots diff --git a/README.md b/README.md index db6c95a5..4ac44d34 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,7 @@ The `tests` directory contains end-to-end tests for the project, utilizing Cypre 1. Run `npm install`. 2. Run `npm run build`. 3. Run `npm run env:start`. -4. Set Mailchimp credentials as environment variables: - - run `export CYPRESS_MAILCHIMP_USERNAME="your mailchimp username"` - - run `export CYPRESS_MAILCHIMP_PASSWORD="your mailchimp password"` - - Do not forget to escape any special characters with `\`. `echo $CYPRESS_MAILCHIMP_PASSWORD` to ensure the password is set correctly. +4. Duplicate the `.env.test.sample` file and rename `.env.test`. Populate this file with your own Mailchimp account credentials. - **NOTE:** Use a test account that does not require 2FA. 2FA will fail some of the tests. 5. Set your Mailchimp account up - Name the audience in your Mailchimp test account "10up". Required for `settings.test.js`. @@ -110,9 +107,6 @@ The `tests` directory contains end-to-end tests for the project, utilizing Cypre 6. Run `npm run cypress:run`. You can also run `npm run cypress:open` to run tests in UI mode. -#### E2E tests notes -- You must run `connect.test.js` before `settings.test.js` in order to log the user in. Otherwise, all settings tests will fail. - ## Support Level **Active:** Mailchimp is actively working on this, and we expect to continue work for the foreseeable future including keeping tested up to the most recent version of WordPress. Bug reports, feature requests, questions, and pull requests are welcome. diff --git a/package-lock.json b/package-lock.json index c7ff5839..992c4e1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,11 @@ }, "devDependencies": { "@10up/cypress-wp-utils": "^0.4.0", + "@mailchimp/mailchimp_marketing": "^3.0.80", "@wordpress/env": "^10.2.0", "10up-toolkit": "^6.2.0", "cypress": "^13.13.2", + "cypress-dotenv": "^3.0.1", "cypress-mochawesome-reporter": "^3.8.2", "mochawesome-json-to-md": "^1.3.5" } @@ -4316,6 +4318,30 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true }, + "node_modules/@mailchimp/mailchimp_marketing": { + "version": "3.0.80", + "resolved": "https://registry.npmjs.org/@mailchimp/mailchimp_marketing/-/mailchimp_marketing-3.0.80.tgz", + "integrity": "sha512-Cgz0xPb+1DUjmrl5whAsmqfAChBko+Wf4/PLQE4RvwfPlcq2agfHr1QFiXEhZ8e+GQwQ3hZQn9iLGXwIXwxUCg==", + "dev": true, + "license": "Apache 2.0", + "dependencies": { + "dotenv": "^8.2.0", + "superagent": "3.8.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@mailchimp/mailchimp_marketing/node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -9112,6 +9138,16 @@ "node": ">=4.0.0" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -9271,6 +9307,13 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/copy-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-1.3.0.tgz", @@ -9958,6 +10001,26 @@ "node": "^16.0.0 || ^18.0.0 || >=20.0.0" } }, + "node_modules/cypress-dotenv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cypress-dotenv/-/cypress-dotenv-3.0.1.tgz", + "integrity": "sha512-k1EGr8JJZdUxTsV7MbnVKGhgiU2q8LsFdDfGfmvofAQTODNhiHnqP7Hp8Cy7fhzVYb/7rkGcto0tPLLr2QCggA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.3.0", + "dotenv-parse-variables": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">= 10.x" + }, + "peerDependencies": { + "cypress": ">= 10.x", + "dotenv": ">= 10.x" + } + }, "node_modules/cypress-mochawesome-reporter": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/cypress-mochawesome-reporter/-/cypress-mochawesome-reporter-3.8.2.tgz", @@ -10693,6 +10756,34 @@ "tslib": "^2.0.3" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-parse-variables": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dotenv-parse-variables/-/dotenv-parse-variables-2.0.0.tgz", + "integrity": "sha512-/Tezlx6xpDqR6zKg1V4vLCeQtHWiELhWoBz5A/E0+A1lXN9iIkNbbfc4THSymS0LQUo8F1PMiIwVG8ai/HrnSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "is-string-and-not-blank": "^0.0.2" + }, + "engines": { + "node": ">= 8.3.0" + } + }, "node_modules/downshift": { "version": "6.1.12", "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.12.tgz", @@ -12672,6 +12763,17 @@ "node": ">= 0.12" } }, + "node_modules/formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -14384,6 +14486,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-string-and-not-blank": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/is-string-and-not-blank/-/is-string-and-not-blank-0.0.2.tgz", + "integrity": "sha512-FyPGAbNVyZpTeDCTXnzuwbu9/WpNXbCfbHXLpCRpN4GANhS00eEIP5Ef+k5HYSNIzIhdN9zRDoBj6unscECvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-string-blank": "^1.0.1" + }, + "engines": { + "node": ">=6.4.0" + } + }, + "node_modules/is-string-blank": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-string-blank/-/is-string-blank-1.0.1.tgz", + "integrity": "sha512-9H+ZBCVs3L9OYqv8nuUAzpcT9OTgMD1yAWrG7ihlnibdkbtB850heAmYWxHuXc4CHy4lKeK69tN+ny1K7gBIrw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", @@ -16553,6 +16675,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -23116,6 +23245,39 @@ "postcss": "^8.3.3" } }, + "node_modules/superagent": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.1.tgz", + "integrity": "sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==", + "deprecated": "Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index bc75b716..c2334836 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,11 @@ }, "devDependencies": { "@10up/cypress-wp-utils": "^0.4.0", + "@mailchimp/mailchimp_marketing": "^3.0.80", "@wordpress/env": "^10.2.0", "10up-toolkit": "^6.2.0", "cypress": "^13.13.2", + "cypress-dotenv": "^3.0.1", "cypress-mochawesome-reporter": "^3.8.2", "mochawesome-json-to-md": "^1.3.5" }, diff --git a/tests/cypress/config.js b/tests/cypress/config.js index 0720819f..69352ebd 100644 --- a/tests/cypress/config.js +++ b/tests/cypress/config.js @@ -1,6 +1,7 @@ const { defineConfig } = require( 'cypress' ); const { loadConfig } = require( '@wordpress/env/lib/config' ); const getCacheDirectory = require( '@wordpress/env/lib/config/get-cache-directory' ); +const dotenvPlugin = require('cypress-dotenv'); module.exports = defineConfig( { chromeWebSecurity: false, @@ -19,6 +20,7 @@ module.exports = defineConfig( { }, e2e: { setupNodeEvents( on, config ) { + config = dotenvPlugin(config, { path: '../../.env.test' }); // Load .env variables into Cypress return setBaseUrl( on, config ); }, specPattern: 'tests/cypress/e2e/**/*.test.{js,jsx,ts,tsx}', diff --git a/tests/cypress/e2e/settings.test.js b/tests/cypress/e2e/settings.test.js index f1d007bb..47fa6136 100644 --- a/tests/cypress/e2e/settings.test.js +++ b/tests/cypress/e2e/settings.test.js @@ -18,11 +18,31 @@ describe('Admin can update plugin settings', () => { cy.login(); // WP cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + // Call mailchimpLists once and store the result in the alias 'mailchimpLists' + cy.getMailchimpLists().then((mailchimpLists) => { + Cypress.env('mailchimpLists', mailchimpLists); // Save globally + }); + }); + + it('All lists from user\'s account populate the WP admin dropdown list', () => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + const $wpLists = cy.get('#mc_list_id'); // Lists from the WP admin dropdown + const mailchimpLists = Cypress.env('mailchimpLists'); + + // Verify that the same number of lists exist in the dropdown as in the Mailchimp account + $wpLists.should('have.length', mailchimpLists.length); + + mailchimpLists.forEach((list) => { + // Verify that all Mailchimp account lists exist in dropdown + cy.get('#mc_list_id').should('contain', list.name); + }); }); it('Admin can see list and save it', () => { cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + // Verify that list can be saved cy.get('.mc-h2').contains('Your Lists'); cy.get('#mc_list_id').select('10up'); cy.get('input[value="Update List"]').click(); @@ -56,7 +76,15 @@ describe('Admin can update plugin settings', () => { // Test here... }); - it('Admin can create a Signup form using the shortcode', () => { + /** + * - Test form creation + * - Test form display (basic) + * - Test form error handling (basic) + * - Test form submission + * - Test that the contact was added to the Mailchimp account via the Mailchimp API + */ + it('Admin can create and submit a Signup form using the shortcode', () => { + // Step 1: Set up the post with the shortcode const postTitle = 'Mailchimp signup form - shortcode'; const beforeSave = () => { cy.insertBlock('core/shortcode').then((id) => { @@ -66,18 +94,43 @@ describe('Admin can update plugin settings', () => { .type('[mailchimpsf_form]'); }); }; + + // Step 2: Create the post cy.createPost({ title: postTitle, content: '', beforeSave }).then((post) => { if (post) { shortcodePostURL = `/?p=${post.id}`; cy.visit(shortcodePostURL); + + // Step 3: Verify the form is displayed cy.get('#mc_signup').should('exist'); cy.get('#mc_mv_EMAIL').should('exist'); cy.get('#mc_signup_submit').should('exist'); - // Test error handling + // Step 4: Test error handling cy.get('#mc_signup_submit').click(); cy.get('.mc_error_msg').should('exist'); cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + + // Step 5: Test that the form can be submitted + // TODO: Is this email address name a security hazard? "@example.com" emails will not pass validation. + const email = 'max.garceau+shortcodesignuptest@10up.com'; + cy.get('#mc_mv_EMAIL').type(email); + cy.get('#mc_signup_submit').click(); + + // Step 6: Verify that the form was submitted successfully + cy.get('.mc_success_msg').should('exist'); + + // // TODO: This is failing because we need to confirm the test email address subscription + // // Step 7: Verify that the contact was added to the Mailchimp account via the Mailchimp API + // const mailchimpLists = Cypress.env('mailchimpLists'); + // const listId = mailchimpLists.find((list) => list.name === '10up').id; + // // Get the contacts from the list + // cy.getContactsFromAList(listId).then((contacts) => { + // console.log('Contacts:', contacts); + // // Verify that the contact was added to the list + // const contactJustRegistered = contacts.find((c) => c.email_address === email); + // expect(contactJustRegistered).to.exist; + // }); } }); }); diff --git a/tests/cypress/e2e/subscribe.test.js b/tests/cypress/e2e/subscribe.test.js index e89476f5..2ed08cc2 100644 --- a/tests/cypress/e2e/subscribe.test.js +++ b/tests/cypress/e2e/subscribe.test.js @@ -21,7 +21,7 @@ describe('Subscribe actions', () => { }); // TODO: This is a known bug. The back link goes to a 404 page on the Mailchimp account. - it('after a double optin subscribe a user can click a back link to return to the website', () => { + it.skip('after a double optin subscribe a user can click a back link to return to the website', () => { }); }); \ No newline at end of file diff --git a/tests/cypress/e2e/unsubscribe.test.js b/tests/cypress/e2e/unsubscribe.test.js index edf7db48..300f31b0 100644 --- a/tests/cypress/e2e/unsubscribe.test.js +++ b/tests/cypress/e2e/unsubscribe.test.js @@ -103,11 +103,11 @@ describe('Unsubscribe form', () => { }); }); - it('does not display an unsubscribe link when the unsubscribe option is disabled', () => { + it.skip('does not display an unsubscribe link when the unsubscribe option is disabled', () => { }); - it('redirects the user back to the website when the user is finished unsubscribing and clicks the back link', () => { + it.skip('redirects the user back to the website when the user is finished unsubscribing and clicks the back link', () => { }); }); \ No newline at end of file diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 42bc2563..68020646 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -25,134 +25,7 @@ // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -const state = {}; - -/** - * Intercepts calls to window.open() to keep a reference to the new window - */ -Cypress.Commands.add('capturePopup', () => { - cy.window().then((win) => { - const { open } = win; - cy.stub(win, 'open').callsFake((...params) => { - // Capture the reference to the popup - state.popup = open(...params); - return state.popup; - }); - }); -}); - -/** - * Returns a wrapped body of a captured popup - */ -Cypress.Commands.add('popup', () => { - const popup = Cypress.$(state.popup.document); - return cy.wrap(popup.contents().find('body')); -}); - -Cypress.Commands.add('mailchimpLogout', () => { - // Logout if already connected. - cy.get('body').then(($body) => { - if ($body.find('input[value="Logout"]').length > 0) { - cy.get('input[value="Logout"]').click(); - } - }); -}); - -/** - * Log into Mailchimp account - * - * Not sure we should put this much logic into one command, but we need - * the Mailchimp login functionality to test settings.test.js independently - */ -Cypress.Commands.add('mailchimpLogin', (username = null, password = null) => { - username = username ?? Cypress.env('MAILCHIMP_USERNAME'); - password = password ?? Cypress.env('MAILCHIMP_PASSWORD'); - - cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); - - // Logout if already connected. - cy.mailchimpLogout(); - - // Check Mailchimp login screen for OAuth login. - cy.get('#mailchimp_sf_oauth_connect').should('exist'); - - // Enable popup capture. - cy.capturePopup(); - - cy.get('#mailchimp_sf_oauth_connect').click(); - cy.wait(6000); - - // Accept cookie consent popup window (if present) - cy.popup().then(($popup) => { - const acceptButtonSelector = '#onetrust-accept-btn-handler'; - - // Check if the accept button is visible and click it - if ($popup.find(acceptButtonSelector).length > 0 && $popup.find(acceptButtonSelector).is(':visible')) { - $popup.find(acceptButtonSelector).click(); - } else { - cy.log('Cookie consent popup not found or not visible.'); - } - }); - - cy.popup() - .find('input#username') - .clear() - .type(username, { force: true }); - cy.popup() - .find('input#password') - .clear() - .type(password, { force: true }); - cy.popup().find('button[type="submit"]').click({ force: true }); - cy.wait(10000); // Not a best practice, but did not find a better way to handle this. - - // DEV NOTE: This is where 2FA would appear. You must test with an account that does not enable 2FA. - - cy.popup().find('input#submitButton').click({ force: true }); - cy.wait(10000); // Not a best practice, but did not find a better way to handle this. -}); - -/** - * Adds a wrapper over the mailchimpLogin command to check if - * a user is already logged in. - * - * This is to increase testing speed - * - * The name is a mouth full, but is named as such to be explicit - */ -Cypress.Commands.add('mailchimpLoginIfNotAlreadyLoggedIn', () => { - // Log into Mailchimp account if we need to. - cy.get('body').then(($body) => { - const hasLogout = $body.find('input[value="Logout"]').length > 0; - if (!hasLogout) { - cy.mailchimpLogin(); - } else { - cy.log('Already logged into Mailchimp account'); - } - }); -}); - -/** - * Checks if MAILCHIMP_USERNAME and MAILCHIMP_PASSWORD environment variables are set. - * Stops the test execution with an error message if either is missing. - */ -Cypress.Commands.add('checkMailchimpEnv', () => { - const username = Cypress.env('MAILCHIMP_USERNAME'); - const password = Cypress.env('MAILCHIMP_PASSWORD'); - - if (!username || !password) { - const errorMessage = ` - [ERROR] Required environment variables are missing: - MAILCHIMP_USERNAME: ${username ? `${username.slice(0, 3)}*****${username.slice(-4)}` : 'NOT SET'} - MAILCHIMP_PASSWORD: ${password ? 'SET' : 'NOT SET'} - - Please set these environment variables as described in the "E2E tests" section - of the readme or through your CI/CD environment to proceed. - `; - - // Log the error message and stop the test - Cypress.log({ name: 'Env Check', message: errorMessage }); - throw new Error(errorMessage); - } - - cy.log('Environment variables for Mailchimp are correctly set.'); -}); +import './commands/popup'; +import './commands/utility'; +import './commands/mailchimpLogin'; +import './commands/mailchimpApi'; diff --git a/tests/cypress/support/commands/mailchimpApi.js b/tests/cypress/support/commands/mailchimpApi.js new file mode 100644 index 00000000..3f4c34ef --- /dev/null +++ b/tests/cypress/support/commands/mailchimpApi.js @@ -0,0 +1,43 @@ +import mailchimp from './mailchimpApiConfig'; + +/** + * Mailchimp API requests and commands + * + * NOTE: Intentionally not caching responses + * - Tests change over time and flexibility should be a priority + * - Caching could create false outcomes in tests that are hard to troubleshoot or be undetectable + * - These functions are not run enough to warrant caching + */ + +/** + * Health Check + */ +Cypress.Commands.add('checkMailchimpApi', checkMailchimpApi); +async function checkMailchimpApi() { + const response = await mailchimp.ping.get(); // Returns 'Everything\'s Chimpy!' + if (response.health_status !== 'Everything\'s Chimpy!') { + throw new Error('Mailchimp API is not working'); + } +} + +/** + * Get all Mailchimp lists from a users account + * + * Gets lists from the account of the API token set in the mailchimp config + */ +Cypress.Commands.add('getMailchimpLists', getAllLists); +async function getAllLists() { + const response = await mailchimp.lists.getAllLists(); + return response.lists; +} + +/** + * Get all Mailchimp lists from a users account + * + * Gets lists from the account of the API token set in the mailchimp config + */ +Cypress.Commands.add('getContactsFromAList', getContactsFromAList); +async function getContactsFromAList(listId) { + const response = await mailchimp.lists.getListMembersInfo(listId); + return response.members; +} diff --git a/tests/cypress/support/commands/mailchimpApiConfig.js b/tests/cypress/support/commands/mailchimpApiConfig.js new file mode 100644 index 00000000..4e1816fb --- /dev/null +++ b/tests/cypress/support/commands/mailchimpApiConfig.js @@ -0,0 +1,29 @@ +// TODO: Not sure this file belongs here, but no better place for it +import mailchimp from '@mailchimp/mailchimp_marketing'; + +/** + * Mailchimp API Config + * Ensures lazy configuration for E2E tests requiring + * Mailchimp account modification. + */ + +// Configure once +let isConfigured = false; + +function ensureConfigured() { + if (!isConfigured) { + mailchimp.setConfig({ + apiKey: Cypress.env('MAILCHIMP_API_KEY'), + server: Cypress.env('MAILCHIMP_API_SERVER_PREFIX'), + }); + isConfigured = true; // Mark as configured + } +} + +// Proxy to intercept property access and ensure configuration +export default new Proxy(mailchimp, { + get(target, prop) { + ensureConfigured(); // Ensure configuration before accessing any property + return target[prop]; // Return the original property + }, +}); \ No newline at end of file diff --git a/tests/cypress/support/commands/mailchimpLogin.js b/tests/cypress/support/commands/mailchimpLogin.js new file mode 100644 index 00000000..c134e90e --- /dev/null +++ b/tests/cypress/support/commands/mailchimpLogin.js @@ -0,0 +1,84 @@ +/** + * Log out of Mailchimp account + */ +Cypress.Commands.add('mailchimpLogout', () => { + // Logout if already connected. + cy.get('body').then(($body) => { + if ($body.find('input[value="Logout"]').length > 0) { + cy.get('input[value="Logout"]').click(); + } + }); +}); + +/** + * Log into Mailchimp account + * + * Not sure we should put this much logic into one command, but we need + * the Mailchimp login functionality to test settings.test.js independently + */ +Cypress.Commands.add('mailchimpLogin', (username = null, password = null) => { + username = username ?? Cypress.env('MAILCHIMP_USERNAME'); + password = password ?? Cypress.env('MAILCHIMP_PASSWORD'); + + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + + // Logout if already connected. + cy.mailchimpLogout(); + + // Check Mailchimp login screen for OAuth login. + cy.get('#mailchimp_sf_oauth_connect').should('exist'); + + // Enable popup capture. + cy.capturePopup(); + + cy.get('#mailchimp_sf_oauth_connect').click(); + cy.wait(6000); + + // Accept cookie consent popup window (if present) + cy.popup().then(($popup) => { + const acceptButtonSelector = '#onetrust-accept-btn-handler'; + + // Check if the accept button is visible and click it + if ($popup.find(acceptButtonSelector).length > 0 && $popup.find(acceptButtonSelector).is(':visible')) { + $popup.find(acceptButtonSelector).click(); + } else { + cy.log('Cookie consent popup not found or not visible.'); + } + }); + + cy.popup() + .find('input#username') + .clear() + .type(username, { force: true }); + cy.popup() + .find('input#password') + .clear() + .type(password, { force: true }); + cy.popup().find('button[type="submit"]').click({ force: true }); + cy.wait(10000); // Not a best practice, but did not find a better way to handle this. + + // DEV NOTE: This is where 2FA would appear. You must test with an account that does not enable 2FA. + + cy.popup().find('input#submitButton').click({ force: true }); + cy.wait(10000); // Not a best practice, but did not find a better way to handle this. +}); + +/** + * Adds a wrapper over the mailchimpLogin command to check if + * a user is already logged in. + * + * This is to increase testing speed + * + * The name is a mouth full, but is named as such to be explicit + */ +Cypress.Commands.add('mailchimpLoginIfNotAlreadyLoggedIn', () => { + // Log into Mailchimp account if we need to. + cy.get('body').then(($body) => { + const hasLogout = $body.find('input[value="Logout"]').length > 0; + if (!hasLogout) { + cy.mailchimpLogin(); + } else { + cy.log('Already logged into Mailchimp account'); + } + }); +}); \ No newline at end of file diff --git a/tests/cypress/support/commands/popup.js b/tests/cypress/support/commands/popup.js new file mode 100644 index 00000000..ae9fc9d7 --- /dev/null +++ b/tests/cypress/support/commands/popup.js @@ -0,0 +1,24 @@ +// Initialize state to store the popup reference +const state = {}; + +/** + * Intercepts calls to window.open() to keep a reference to the new window + */ +Cypress.Commands.add('capturePopup', () => { + cy.window().then((win) => { + const { open } = win; + cy.stub(win, 'open').callsFake((...params) => { + // Capture the reference to the popup + state.popup = open(...params); + return state.popup; + }); + }); +}); + +/** + * Returns a wrapped body of a captured popup + */ +Cypress.Commands.add('popup', () => { + const popup = Cypress.$(state.popup.document); + return cy.wrap(popup.contents().find('body')); +}); \ No newline at end of file diff --git a/tests/cypress/support/commands/utility.js b/tests/cypress/support/commands/utility.js new file mode 100644 index 00000000..aa41cb8b --- /dev/null +++ b/tests/cypress/support/commands/utility.js @@ -0,0 +1,29 @@ +/** + * Checks if MAILCHIMP_USERNAME and MAILCHIMP_PASSWORD environment variables are set. + * Stops the test execution with an error message if either is missing. + */ +Cypress.Commands.add('checkMailchimpEnv', () => { + const username = Cypress.env('MAILCHIMP_USERNAME'); + const password = Cypress.env('MAILCHIMP_PASSWORD'); + const apiKey = Cypress.env('MAILCHIMP_API_KEY'); + const serverPrefix = Cypress.env('MAILCHIMP_API_SERVER_PREFIX'); + + if (!username || !password || !apiKey || !serverPrefix) { + const errorMessage = ` + [ERROR] Required environment variables are missing: + MAILCHIMP_USERNAME: ${username ? `${username.slice(0, 3)}*****${username.slice(-4)}` : 'NOT SET'} + MAILCHIMP_PASSWORD: ${password ? 'SET' : 'NOT SET'} + MAILCHIMP_API_KEY: ${apiKey ? `${apiKey.slice(0, 3)}*****${apiKey.slice(-4)}` : 'NOT SET'} + MAILCHIMP_API_SERVER_PREFIX: ${serverPrefix ? `${serverPrefix}` : 'NOT SET'} + + Please set these environment variables as described in the "E2E tests" section + of the readme or through your CI/CD environment to proceed. + `; + + // Log the error message and stop the test + Cypress.log({ name: 'Env Check', message: errorMessage }); + throw new Error(errorMessage); + } + + cy.log('Environment variables for Mailchimp are correctly set.'); +}); \ No newline at end of file diff --git a/tests/cypress/support/index.js b/tests/cypress/support/index.js index 747160d3..2974c3eb 100644 --- a/tests/cypress/support/index.js +++ b/tests/cypress/support/index.js @@ -21,6 +21,7 @@ import './commands'; before(() => { // Add global setup logic here cy.checkMailchimpEnv(); // Example: Check environment variables + cy.checkMailchimpApi(); // Throw error if we can't connect to the API cy.log('Global setup completed!'); });