diff --git a/.env.test.sample b/.env.test.sample new file mode 100644 index 00000000..86f55e92 --- /dev/null +++ b/.env.test.sample @@ -0,0 +1,11 @@ +## +## Users +## + +# Main user for testing +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 10f58514..2da1e229 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 @@ -16,4 +17,4 @@ tests/cypress/downloads mailchimp.zip # IDE -.vscode \ No newline at end of file +.vscode diff --git a/README.md b/README.md index c37d1e03..4ac44d34 100644 --- a/README.md +++ b/README.md @@ -99,10 +99,13 @@ 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"` -5. Run `npm run cypress:run`. You can also run `npm run cypress:open` to run tests in UI mode. +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`. + - Enable all merge fields. From your Mailchimp account home page -> `/audience/settings/` -> Edit merge fields/tags -> Set all merge fields to "visible". Required for `settings.test.js`. + +6. Run `npm run cypress:run`. You can also run `npm run cypress:open` to run tests in UI mode. ## Support Level diff --git a/package-lock.json b/package-lock.json index 62477fa6..ceedfbe7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,11 @@ }, "devDependencies": { "@10up/cypress-wp-utils": "^0.5.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" } @@ -4317,6 +4319,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", @@ -9113,6 +9139,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", @@ -9273,6 +9309,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", @@ -9960,6 +10003,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", @@ -10695,6 +10758,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", @@ -12679,6 +12770,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", @@ -14391,6 +14493,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", @@ -16560,6 +16682,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", @@ -23124,6 +23253,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 62076fd9..92eaaf8a 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,11 @@ }, "devDependencies": { "@10up/cypress-wp-utils": "^0.5.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/bin/initialize.sh b/tests/bin/initialize.sh index f9fbe6ab..a9348ccc 100644 --- a/tests/bin/initialize.sh +++ b/tests/bin/initialize.sh @@ -3,3 +3,6 @@ set -e npm run env run tests-wordpress chmod -- -c ugo+w /var/www/html npm run env run tests-cli wp rewrite structure '/%postname%/' -- --hard + +wp-env run tests-cli wp post create --post_title='Mailchimp signup form – shortcode' --post_type=page --post_status=publish --post_author=1 --post_content='[mailchimpsf_form]' +wp-env run tests-cli wp post create --post_title='Mailchimp signup form - Block' --post_type=page --post_status=publish --post_author=1 --post_content='' diff --git a/tests/cypress/config.js b/tests/cypress/config.js index da04ab13..07b91585 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: [ @@ -30,6 +32,7 @@ module.exports = defineConfig( { ], supportFile: 'tests/cypress/support/index.js', defaultCommandTimeout: 20000, + experimentalRunAllSpecs: true, }, retries: { runMode: 2, diff --git a/tests/cypress/e2e/connect.test.js b/tests/cypress/e2e/connect.test.js index eecbb4d1..78eeae4e 100644 --- a/tests/cypress/e2e/connect.test.js +++ b/tests/cypress/e2e/connect.test.js @@ -14,42 +14,15 @@ describe('Admin can connect to "Mailchimp" Account', () => { } }); - // Check Mailchimp menu. - 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(Cypress.env('MAILCHIMP_USERNAME'), { force: true }); - cy.popup() - .find('input#password') - .clear() - .type(Cypress.env('MAILCHIMP_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. - - 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. + // Mailchimp connection login as a command in order to be reusable + cy.mailchimpLogin(); + // Logout exists cy.get('.mc-user h3').contains('Logged in as: '); cy.get('input[value="Logout"]').should('exist'); + + // Mailchimp lists exists and has at least one audience + cy.get('#mc_list_id').should('exist'); + cy.get('#mc_list_id').children().should('have.length.greaterThan', 1); // The " — Select A List — " default option will always be present }); }); diff --git a/tests/cypress/e2e/admin.test.js b/tests/cypress/e2e/settings/admin.test.js similarity index 95% rename from tests/cypress/e2e/admin.test.js rename to tests/cypress/e2e/settings/admin.test.js index 06867491..5df2e46a 100644 --- a/tests/cypress/e2e/admin.test.js +++ b/tests/cypress/e2e/settings/admin.test.js @@ -23,6 +23,9 @@ describe('Admin can login and make sure plugin is activated', () => { it('Admin can see "Create account" button and Can visit "Create account" settings page.', () => { cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + // If logged in then log out + cy.mailchimpLogout(); + // Check Create account button. cy.get('.button.mailchimp-sf-button.button-secondary').should('be.visible'); cy.get('.button.mailchimp-sf-button.button-secondary').contains('Create an account'); @@ -35,6 +38,9 @@ describe('Admin can login and make sure plugin is activated', () => { it("Admin shouldn't be able to submit create account form with invalid data", () => { cy.visit('/wp-admin/admin.php?page=mailchimp_sf_create_account'); + // If logged in then log out + cy.mailchimpLogout(); + // Submit form without filling any data. cy.get('#mailchimp-sf-create-activate-account').click(); diff --git a/tests/cypress/e2e/settings/list.test.js b/tests/cypress/e2e/settings/list.test.js new file mode 100644 index 00000000..311e3c21 --- /dev/null +++ b/tests/cypress/e2e/settings/list.test.js @@ -0,0 +1,61 @@ +/* eslint-disable no-undef */ +describe('Mailchimp lists ', () => { + let shortcodePostURL; + let blockPostPostURL; + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + shortcodePostURL = urls.shortcodePostURL; + blockPostPostURL = urls.blockPostPostURL; + }); + + 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 > option[value]:not([value=""])'); // 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.selectList('10up'); + cy.get('#mc-message .success_msg b').contains('Success!'); + + // Verify that the settings are visible if a list is saved + cy.get('input[value="Update Subscribe Form Settings"]').should('exist'); + }); + + it('Admin that has never saved a list can not see the form on the front end', () => { + cy.wpCli('wp option delete mc_list_id').then(() => { + cy.visit(shortcodePostURL); + cy.get('#mc_signup_form').should('not.exist'); + + cy.visit(blockPostPostURL); + cy.get('#mc_signup_form').should('not.exist'); + + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.selectList('10up'); + }); + }); +}); diff --git a/tests/cypress/e2e/settings/settings.test.js b/tests/cypress/e2e/settings/settings.test.js new file mode 100644 index 00000000..40b0e98b --- /dev/null +++ b/tests/cypress/e2e/settings/settings.test.js @@ -0,0 +1,309 @@ +/* eslint-disable no-undef */ +describe('Admin can update plugin settings', () => { + let shortcodePostURL; + let blockPostPostURL; + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + ({ shortcodePostURL, blockPostPostURL } = urls); + }); + + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + cy.selectList('10up'); // Ensure a list is selected + }); + + it('Admin can set content options for signup form', () => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + + // Set content options + const header = 'Subscribe to our newsletter'; + const subHeader = + 'Join our mailing list to receive the latest news and updates from our team.'; + const button = 'Subscribe Now'; + cy.get('#mc_header_content').clear().type(header); + cy.get('#mc_subheader_content').clear().type(subHeader); + cy.get('#mc_submit_text').clear().type(button); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify content options + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('.mc_custom_border_hdr').contains(header); + cy.get('#mc_subheader').contains(subHeader); + cy.get('#mc_signup_submit').contains(button); + }); + }); + + it('Admin can remove mailchimp CSS', () => { + // Remove mailchimp CSS. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_nuke_all_styles').check(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_subheader').should('not.have.css', 'margin-bottom', '18px'); + }); + + // Enable mailchimp CSS. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_nuke_all_styles').uncheck(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_subheader').should('have.css', 'margin-bottom', '18px'); + }); + }); + + it('Admin can set custom styling on signup form', () => { + // Enable custom styling and set values. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_custom_style').check(); + cy.get('#mc_form_border_width').clear().type('10'); + cy.get('#mc_form_border_color').clear().type('000000'); + cy.get('#mc_form_text_color').clear().type('FF0000'); + cy.get('#mc_form_background').clear().type('00FF00'); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_signup form').should('have.css', 'border-width', '10px'); + cy.get('#mc_signup form').should('have.css', 'border-color', 'rgb(0, 0, 0)'); + cy.get('#mc_signup form').should('have.css', 'color', 'rgb(255, 0, 0)'); + cy.get('#mc_signup form').should('have.css', 'background-color', 'rgb(0, 255, 0)'); + + // Form is able to be submitted with custom styles + cy.get('#mc_signup_submit') + .scrollIntoView({ offset: { top: -100, left: 0 } }) + .should('be.visible') // Check if the button is visible + .and('not.be.disabled'); // Ensure the button is not disabled + + // Ensure that custom CSS does not cover submit button + cy.get('#mc_signup_submit').then(($el) => { + const rect = $el[0].getBoundingClientRect(); + + // Check that the element is within the viewport + cy.window().then((win) => { + const windowHeight = win.innerHeight; + const windowWidth = win.innerWidth; + + expect(rect.top).to.be.greaterThan(0); + expect(rect.left).to.be.greaterThan(0); + expect(windowHeight).to.be.greaterThan(0); + expect(windowWidth).to.be.greaterThan(0); + expect(rect.bottom).to.be.lessThan(windowHeight); + expect(rect.right).to.be.lessThan(windowWidth); + }); + + // Check if the center of the element is not covered by another element + const centerX = rect.x + $el[0].offsetWidth / 2; + const centerY = rect.y + $el[0].offsetHeight / 2; + + cy.document().then((doc) => { + const topElement = doc.elementFromPoint(centerX, centerY); + expect(topElement).to.equal($el[0]); + }); + }); + }); + + // Reset + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_custom_style').uncheck(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify base styles + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_signup form').should('not.have.css', 'border-width', '10px'); + cy.get('#mc_signup form').should('not.have.css', 'border-color', 'rgb(0, 0, 0)'); + cy.get('#mc_signup form').should('not.have.css', 'color', 'rgb(255, 0, 0)'); + cy.get('#mc_signup form').should('not.have.css', 'background-color', 'rgb(0, 255, 0)'); + }); + }); + + it('Admin can set Merge Fields Included settings', () => { + // Ensure that all current merge tags are up to date and saved + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_list_id').select('10up'); + cy.get('input[value="Update List"]').click(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Uncheck all optional merge fields + cy.get('#mc_mv_FNAME').uncheck(); + cy.get('#mc_mv_LNAME').uncheck(); + cy.get('#mc_mv_ADDRESS').uncheck(); + cy.get('#mc_mv_BIRTHDAY').uncheck(); + cy.get('#mc_mv_COMPANY').uncheck(); + cy.get('#mc_mv_PHONE').uncheck(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_mv_FNAME').should('not.exist'); + cy.get('#mc_mv_LNAME').should('not.exist'); + cy.get('#mc_mv_ADDRESS-addr1').should('not.exist'); // The address field has several inputs + cy.get('#mc_mv_BIRTHDAY').should('not.exist'); + cy.get('#mc_mv_COMPANY').should('not.exist'); + cy.get('#mc_mv_PHONE').should('not.exist'); + }); + + // Reset and recheck all merge fields + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_mv_FNAME').check(); + cy.get('#mc_mv_LNAME').check(); + cy.get('#mc_mv_ADDRESS').check(); + cy.get('#mc_mv_BIRTHDAY').check(); + cy.get('#mc_mv_COMPANY').check(); + cy.get('#mc_mv_PHONE').check(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_mv_FNAME').should('exist'); + cy.get('#mc_mv_LNAME').should('exist'); + cy.get('#mc_mv_ADDRESS-addr1').should('exist'); // The address field has several inputs + cy.get('#mc_mv_BIRTHDAY').should('exist'); + cy.get('#mc_mv_COMPANY').should('exist'); + cy.get('#mc_mv_PHONE').should('exist'); + }); + }); + + /** + * NOTE: "Use Double Opt-In (Recommended)?" and "Update existing subscribers?" + * are handled in `subscribe.test.js` + */ + it('Admin can set list options settings', () => { + // Remove mailchimp JavaScript support. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_use_javascript').uncheck(); + cy.get('#mc_use_datepicker').uncheck(); + cy.get('#mc_use_unsub_link').check(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_submit_type').should('have.value', 'html'); + cy.get('#mc_mv_BIRTHDAY').should('not.have.class', 'hasDatepicker'); + cy.get('#mc_mv_BIRTHDAY').click(); + cy.get('#ui-datepicker-div').should('not.exist'); + cy.get('#mc_unsub_link').should('exist'); + }); + + // Reset + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_use_javascript').check(); + cy.get('#mc_use_datepicker').check(); + cy.get('#mc_use_unsub_link').uncheck(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_submit_type').should('have.value', 'js'); + cy.get('#mc_mv_BIRTHDAY').should('have.class', 'hasDatepicker'); + cy.get('#mc_mv_BIRTHDAY').click(); + cy.get('#ui-datepicker-div').should('exist'); + cy.get('#mc_unsub_link').should('not.exist'); + }); + }); + + // TODO: Add case for separate account login and settings get reset. + it('Ensure settings persist between logging out and logging back in of Mailchimp account', () => { + // Step 1: Visit Mailchimp settings page + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mailchimp_sf_oauth_connect').should('not.exist'); + + // Step 2: Set an option different from the default + const customHeader = 'My Custom Header'; + cy.get('#mc_header_content').clear().type(customHeader); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify the custom header is saved + cy.get('#mc-message .success_msg').contains( + 'Successfully Updated your List Subscribe Form Settings!', + ); + cy.get('#mc_header_content').should('have.value', customHeader); + + // Step 3: Log out of the Mailchimp account + cy.get('input[value="Logout"]').click(); + + // Verify the logout was successful + cy.get('#mailchimp_sf_oauth_connect').should('exist'); + + // Step 4: Log in back + cy.mailchimpLogin(); + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + + // Step 5: Ensure the original settings persist + cy.get('#mc_header_content').should('have.value', customHeader); + }); + + it('The default settings populate as expected', () => { + const options = [ + 'mc_header_content', + 'mc_subheader_content', + 'mc_submit_text', + 'mc_nuke_all_styles', + 'mc_custom_style', + 'mc_form_border_width', + 'mc_form_border_color', + 'mc_form_background', + 'mc_form_text_color', + 'mc_update_existing', + 'mc_double_optin', + 'mc_user_id', + 'mc_use_javascript', + 'mc_use_datepicker', + 'mc_use_unsub_link', + 'mc_list_id', + 'mc_list_name', + 'mc_interest_groups', + 'mc_merge_vars', + ]; + + // Clear all options + cy.getListId('10up').then((listId) => { + cy.getMergeFields(listId).then((mergeFields) => { + const mergeFieldOptions = mergeFields.map((field) => `mc_mv_${field.tag}`); + options.push(...mergeFieldOptions); + cy.wpCli(`wp option delete ${options.join(' ')}`).then(() => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.mailchimpLogout(); + cy.mailchimpLogin(); + cy.get('.mc-user h3').contains('Logged in as: '); + cy.get('input[value="Logout"]').should('exist'); + + cy.selectList('10up'); + + // Verify default settings + cy.get('#mc_header_content').should('have.value', 'Sign up for 10up'); + cy.get('#mc_subheader_content').should('have.value', ''); + cy.get('#mc_submit_text').should('have.value', 'Subscribe'); + cy.get('#mc_nuke_all_styles').should('not.be.checked'); + cy.get('#mc_custom_style').should('not.be.checked'); + cy.get('#mc_form_border_width').should('have.value', '1'); + cy.get('#mc_form_border_color').should('have.value', 'E0E0E0'); + cy.get('#mc_form_background').should('have.value', 'FFFFFF'); + cy.get('#mc_form_text_color').should('have.value', '3F3F3f'); + cy.get('#mc_update_existing').should('not.be.checked'); + cy.get('#mc_double_optin').should('be.checked'); + cy.get('#mc_use_unsub_link').should('not.be.checked'); + cy.get('#mc_mv_FNAME').should('be.checked'); + cy.get('#mc_mv_LNAME').should('be.checked'); + cy.get('#mc_mv_ADDRESS').should('be.checked'); + cy.get('#mc_mv_BIRTHDAY').should('be.checked'); + cy.get('#mc_mv_COMPANY').should('be.checked'); + cy.get('#mc_mv_PHONE').should('be.checked'); + }); + }); + }); + }); +}); diff --git a/tests/cypress/e2e/submission/double-opt-in-subscribe.test.js b/tests/cypress/e2e/submission/double-opt-in-subscribe.test.js new file mode 100644 index 00000000..f89f011e --- /dev/null +++ b/tests/cypress/e2e/submission/double-opt-in-subscribe.test.js @@ -0,0 +1,30 @@ +/* eslint-disable no-undef */ +// TODO: BLOCKED - Need access to a service that can catch test emails so we can finish the email verification process. +describe.skip('Double Opt-in Subscriptions', () => { + let shortcodePostURL; + let blockPostPostURL; + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + shortcodePostURL = urls.shortcodePostURL; + blockPostPostURL = urls.blockPostPostURL; + }); + + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + cy.selectList('10up'); // Ensure list is selected, refreshes Mailchimp data with WP + + cy.setDoubleOptInOption(false); + }); + + it.skip('When double opt-in is enabled a subscriber must verify their email before their submission displays in Mailchimp', () => { + // Write test... + }); + + // TODO: This is a known bug. The back link goes to a 404 page on the Mailchimp account. + it.skip('after a double optin subscribe a user can click a back link to return to the website', () => { + // Write test... + }); +}); diff --git a/tests/cypress/e2e/submission/js-submission.test.js b/tests/cypress/e2e/submission/js-submission.test.js new file mode 100644 index 00000000..11fbaeef --- /dev/null +++ b/tests/cypress/e2e/submission/js-submission.test.js @@ -0,0 +1,107 @@ +/* eslint-disable no-undef */ +import { generateRandomEmail } from '../../support/functions/utility'; + +describe('JavaScript submission', () => { + let blockPostPostURL; + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + blockPostPostURL = urls.blockPostPostURL; + }); + + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + cy.selectList('10up'); // Ensure list is selected, refreshes Mailchimp data with WP + + // Set JS support to enabled + cy.setJavaScriptOption(true); + + // Disable double opt-in + cy.setDoubleOptInOption(false); + + // Disable all merge fields + cy.toggleMergeFields('uncheck'); + }); + + beforeEach(() => { + cy.visit(blockPostPostURL); + setUpForm(); + }); + + function setUpForm() { + // Step 1: Assert form contains setup elements + // Email + cy.get('#mc_signup').should('exist'); + cy.get('#mc_mv_EMAIL').should('exist'); + cy.get('#mc_signup_submit').should('exist'); + } + + function submitEmail(email) { + // Step 2: Fill in the required fields (email and other merge fields) + cy.get('#mc_mv_EMAIL').type(email); + + // Step 3: Assert that the submit button is enabled and exists + cy.get('#mc_signup_submit').should('exist').and('be.enabled'); + + // Step 4: Submit the form + cy.get('#mc_signup_submit').click(); + } + + it('Disables the submit button before attempting submission', () => { + submitEmail('invalidemail@--'); // Submit blank email + + // Step 4: Assert that the submit button is disabled after submitting the form + cy.get('#mc_signup_submit').should('be.disabled'); + + // Step 5: Verify that the form submission failed + cy.get('.mc_error_msg').should('exist'); + }); + + it('Perform post submit actions after successful submission', () => { + const email = generateRandomEmail('javascript-submission-post-submit'); + submitEmail(email); + + // Step 5: Assert that the success message is displayed + cy.get('.mc_success_msg').should('exist').contains('success', { matchCase: false }); + + // Step 6: Verify that the form fields are cleared + cy.get('#mc_mv_EMAIL').should('have.value', ''); + + // Step 7: Verify that the submit button is re-enabled + cy.get('#mc_signup_submit').should('be.enabled'); + + cy.wait(1000); + + // Step 8: Assert that the form scrolled to the top + cy.window().then((win) => { + const scrollTop = win.pageYOffset || win.document.documentElement.scrollTop; + expect(scrollTop).to.be.lessThan(500); // Doesn't scroll all the way to the top + }); + + // Step 9: Cleanup and delete contact + cy.deleteContactFromList(email); + }); + + // TODO: This is a bug and is currently broken + it.skip('Persist form data on Mailchimp API validation failure', () => { + // Write test... + }); + + // TODO: BUG: Single opt-in is currently broken, but a fix is scheduled for 1.7.0 + it.skip('Success submission with JS support adds email to Mailchimp account as contact', () => { + const email = generateRandomEmail('javascript-submission-verify-submission'); + submitEmail(email); + + // Step 5: Assert Mailchimp WP Success + cy.get('.mc_success_msg').should('exist').contains('success', { matchCase: false }); + + // Step 6: Verify that the contact was added to the Mailchimp account via the Mailchimp API + cy.wait(5000) + .verifyContactInMailchimp(email); + + // Step 7: Cleanup and delete contact + cy.deleteContactFromList(email); + }); +}); \ No newline at end of file diff --git a/tests/cypress/e2e/submission/resubscribe.test.js b/tests/cypress/e2e/submission/resubscribe.test.js new file mode 100644 index 00000000..a38d0cf8 --- /dev/null +++ b/tests/cypress/e2e/submission/resubscribe.test.js @@ -0,0 +1,26 @@ +/* eslint-disable no-undef */ +describe.skip('Resubscribe actions', () => { + let shortcodePostURL; + let blockPostPostURL; + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + shortcodePostURL = urls.shortcodePostURL; + blockPostPostURL = urls.blockPostPostURL; + }); + + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + cy.selectList('10up'); // Ensure list is selected, refreshes Mailchimp data with WP + + cy.setDoubleOptInOption(false); + }); + + // TODO: This is an enhancement idea for the future + // Mailchimp does not allow unsubscribed contacts to be resubscribed through the API + it.skip('Subscribers who have previously unsubscribed should receive a link to the Mailchimp self hosted sign up form', () => { + // Write test... + }); +}); \ No newline at end of file diff --git a/tests/cypress/e2e/submission/subscribe.test.js b/tests/cypress/e2e/submission/subscribe.test.js new file mode 100644 index 00000000..910b8af5 --- /dev/null +++ b/tests/cypress/e2e/submission/subscribe.test.js @@ -0,0 +1,86 @@ +/* eslint-disable no-undef */ +import { generateRandomEmail } from '../../support/functions/utility'; + +describe('Subscribe actions', () => { + before(() => { + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + cy.selectList('10up'); // Ensure list is selected, refreshes Mailchimp data with WP + + cy.setDoubleOptInOption(false); + }); + + function signUpAndVerify(url) { + cy.visit(url); + + // 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'); + + // 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 + const email = generateRandomEmail('shortcode-signup-test'); + cy.get('#mc_mv_EMAIL').type(email); + + // Step 6: Verify that the form was submitted successfully + cy.submitFormAndVerifyWPSuccess(); + + // // TODO: This is failing because of a bug causing single opt-in to malfunction. Fix is ready for 1.7.0. + // // Step 7: Verify that the contact was added to the Mailchimp account via the Mailchimp API + // cy.verifyContactInMailchimp(email); + + // Step 8: Cleanup and delete contact + cy.deleteContactFromList(email); + } + + /** + * - 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 subscribe to 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) => { + cy.getBlockEditor() + .find(`#${id} .blocks-shortcode__textarea`) + .clear() + .type('[mailchimpsf_form]'); + }); + }; + + // Step 2: Create the post + cy.createPost({ title: postTitle, content: '', beforeSave }).then((post) => { + if (post) { + const shortcodePostURL = `/?p=${post.id}`; + signUpAndVerify(shortcodePostURL); + } + }); + }); + + it('Admin can create and subscribe to a signup form using the Mailchimp block', () => { + // Step 1: Set up the post with the shortcode + const postTitle = 'Mailchimp signup form - Block'; + const beforeSave = () => { + cy.insertBlock('mailchimp/mailchimp', 'Mailchimp List Subscribe Form'); + cy.wait(500); + }; + + // Step 2: Create the post + cy.createPost({ title: postTitle, content: '', beforeSave }).then((postBlock) => { + if (postBlock) { + const blockPostPostURL = `/?p=${postBlock.id}`; + signUpAndVerify(blockPostPostURL); + } + }); + }); +}); diff --git a/tests/cypress/e2e/submission/unsubscribe.test.js b/tests/cypress/e2e/submission/unsubscribe.test.js new file mode 100644 index 00000000..76f79883 --- /dev/null +++ b/tests/cypress/e2e/submission/unsubscribe.test.js @@ -0,0 +1,137 @@ +/* eslint-disable no-undef */ +import { generateRandomEmail } from '../../support/functions/utility'; + +describe('Unsubscribe form', () => { + let shortcodePostURL; + let blockPostPostURL; + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + shortcodePostURL = urls.shortcodePostURL; + blockPostPostURL = urls.blockPostPostURL; + }); + + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + cy.selectList('10up'); // Ensure list is selected, refreshes Mailchimp data with WP + + // Single Opt-in + cy.setDoubleOptInOption(false); + + // Check unsubscription link + cy.setSettingsOption('#mc_use_unsub_link', true); + }); + + after(() => { + // I don't know why we have to login again, but we do + cy.login(); // WP + + // Uncheck unsubscription link + cy.setSettingsOption('#mc_use_unsub_link', false); + }); + + it('unsubscribe link appears on both shortcode and block pages', () => { + [shortcodePostURL, blockPostPostURL].forEach((url) => { + // Visit the mailchimp block page + cy.visit(url); + + // Assert unsubscribe link exists + cy.get('a[href*="/unsubscribe"]').should('exist'); + }); + }) + + it('unsubscribes valid emails that were previously subscribed to a list', () => { + const email = generateRandomEmail('previously-subscribed-email'); + + // Subscribe email to setup test + cy.subscribeToListByName(email); + + let baseUrl; + + // Visit the mailchimp block page + cy.visit(blockPostPostURL); + + // Get baseUrl to use for later assertion + cy.url().then((url) => { + // Extract the base URL + const urlObject = new URL(url); + baseUrl = `${urlObject.protocol}//${urlObject.host}`; + }); + + + // Assert unsubscribe link exists + cy.get('a[href*="/unsubscribe"]').should('exist'); + + // Visit unsubscribe link + cy.get('a[href*="/unsubscribe"]') + .invoke('removeAttr', 'target') // Prevent opening in new window so that Cypress can test + .click(); + + // Unsubscribe + cy.get('#email-address').type(email); + cy.get('input[type="submit"]').click(); + cy.get('body').should('contain', 'Unsubscribe Successful'); + + // Navigate back to the website button exists + cy.contains('a', 'return to our website') + .should('exist'); + + // Verify contact exists in Mailchimp with status 'unsubscribed' + cy.verifyContactInMailchimp(email, '10up', 'unsubscribed').then((contact) => { + cy.verifyContactStatus(contact, 'unsubscribed'); + + // Delete contact to clean up + cy.deleteContactFromList(email); + }); + + // Navigate to back website + // NOTE: The website URL is site in Mailchimp and it won't accept localhost or our test URL + // TODO: Assert that we're back on our website (we currently have no way to set this) + // cy.contains('a', 'return to our website').click(); + // cy.url().should('include', baseUrl); // TODO: Do we want to assert a specific landing page? + }); + + it('throws an error when unsubscribing an email that was never subscribed to a list', () => { + const email = generateRandomEmail('never-subscribed-user'); + + // Visit the mailchimp block page + cy.visit(blockPostPostURL); + + // Assert unsubscribe link exists + cy.get('a[href*="/unsubscribe"]').should('exist'); + + // Visit unsubscribe link + cy.get('a[href*="/unsubscribe"]') + .invoke('removeAttr', 'target') // Prevent opening in new window so that Cypress can test + .click(); + + // Unsubscribe + cy.get('#email-address').type(email); + cy.get('input[type="submit"]').click(); + + // Assert that the unsubscribe didn't work because the email isn't subscribed + cy.get('.errorText').should('contain', 'this email is not subscribed'); + }); + + it('does not display an unsubscribe link when the unsubscribe option is disabled', () => { + // Not sure why we have to login for this test, but we do + cy.login(); // WP + + // Uncheck unsubscription link + cy.setSettingsOption('#mc_use_unsub_link', false); + + [shortcodePostURL, blockPostPostURL].forEach((url) => { + // Visit the mailchimp block page + cy.visit(url); + + // Assert unsubscribe link exists + cy.get('a[href*="/unsubscribe"]').should('not.exist'); + }); + }); + + // NOTE: We can not set the "return to website" URL from the Mailchimp plugin or through the API. + // Alternative proposals on issue #91 and #92 to add a user tutorial + // 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/e2e/submission/update-subscribers.test.js b/tests/cypress/e2e/submission/update-subscribers.test.js new file mode 100644 index 00000000..59cc9794 --- /dev/null +++ b/tests/cypress/e2e/submission/update-subscribers.test.js @@ -0,0 +1,116 @@ +/* eslint-disable no-undef */ +describe('Update Existing Subscriber?', () => { + let blockPostPostURL; + const email = 'existing-subscriber@10up.com'; // Static email, exists in test account already + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + blockPostPostURL = urls.blockPostPostURL; + }); + + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + cy.setDoubleOptInOption(false); + + // Set up for "Update Existing Subscribers?" + cy.setSettingsOption('#mc_update_existing', true); + cy.setMergeFieldsRequired(false); // No required merge fields (selects 10up list too) + cy.toggleMergeFields('check'); // All merge fields are included + + // If subscribed contact doesn't exist then create it + cy.getContactFromList(email).then((contact) => { + if (!contact) { + cy.subscribeToListByName(email); + console.log(`Creating ${email}`); + } else { + console.log(`${email} exists`); + } + }); + }); + + function generateRandomString(length = 10) { + return Math.random() + .toString(36) + .substring(2, 2 + length); + } + + it('Update existing subscribers when they resubmit the signup form if option is checked', () => { + // Navigate to the shortcode post + cy.visit(blockPostPostURL); + + // Generate random strings + const firstName = generateRandomString(); + const lastName = generateRandomString(); + + // Fill the form and submit it + cy.get('#mc_mv_EMAIL').clear().type(email); + cy.get('#mc_mv_FNAME').clear().type(firstName); + cy.get('#mc_mv_LNAME').clear().type(lastName); + + // Submit and assert success + cy.submitFormAndVerifyWPSuccess(); + + // Verify subscriber data is updated in Mailchimp + cy.verifyContactInMailchimp(email).then((contact) => { + cy.verifyContactStatus(contact, 'subscribed'); + expect(contact.merge_fields.FNAME).to.equal(firstName); + expect(contact.merge_fields.LNAME).to.equal(lastName); + }); + }); + + it('Verify that existing subscriber data is updated accurately without creating duplicate records', () => { + // Navigate to the shortcode post + cy.visit(blockPostPostURL); + + // Generate random strings + const firstName = generateRandomString(); + const lastName = generateRandomString(); + + // Fill the form and submit it + cy.get('#mc_mv_EMAIL').clear().type(email); + cy.get('#mc_mv_FNAME').clear().type(firstName); + cy.get('#mc_mv_LNAME').clear().type(lastName); + + // Submit and assert success + cy.submitFormAndVerifyWPSuccess(); + + // Verify a duplicate contact has not been created + cy.getListId('10up').then((listId) => { + cy.getContactsFromAList(listId).then((contacts) => { + const filteredByEmail = contacts.filter( + (contact) => contact.email_address === email, + ); + + expect(filteredByEmail.length).to.equal(1); // Only one match found + expect(filteredByEmail[0].email_address).to.equal(email); // The one match is our email + }); + }); + }); + + // TODO: This test is correct, but failing to a bug allowing contacts to be updated + // regardless of the "Update Existing Subscriber?" option + // Fix in issue 113 scheduled for 1.7.0. + it('Do not update existing subscribers when they resubmit the signup form if option is unchecked', () => { + // Not sure why we have to log in here, but we do + cy.login(); // WP + + // Write test... + cy.setSettingsOption('#mc_update_existing', false); + + // Navigate to the shortcode post + cy.visit(blockPostPostURL); + + // Fill the form and submit it + cy.get('#mc_mv_EMAIL').clear().type(email); + cy.get('#mc_mv_FNAME').clear().type('Should not submit'); + cy.get('#mc_mv_LNAME').clear().type('Should not submit'); + + // Verify error + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains( + /This email address has already been subscribed to this list./i, + ); + }); +}); diff --git a/tests/cypress/e2e/validation/address.test.js b/tests/cypress/e2e/validation/address.test.js new file mode 100644 index 00000000..03bc31fd --- /dev/null +++ b/tests/cypress/e2e/validation/address.test.js @@ -0,0 +1,94 @@ +/* eslint-disable no-undef */ +import { generateRandomEmail } from '../../support/functions/utility'; + +/** + * Test Suite for Address Field Validation + * Includes setup and testing for both JavaScript-enabled and disabled states. + */ +describe('Address Field Validation', () => { + const validAddresses = [ + { addr1: '123 Main St', city: 'Springfield', state: 'IL', zip: '62701', country: 'USA' }, + { addr1: '456 Elm St', city: 'Smallville', state: 'KS', zip: '66002', country: 'USA' }, + ]; + + const invalidAddresses = [ + { addr1: '', city: 'Springfield' }, // Missing Addr 1 + { addr1: '123 Main St', city: '' }, // Missing City + ]; + + let blockPostPostURL; + + before(() => { + // Setup: Log in and configure the form + cy.login(); // Log into WordPress + cy.mailchimpLoginIfNotAlreadyLoggedIn(); // Log into Mailchimp + + // Load post URLs for shortcode and block post tests + cy.fixture('postUrls').then((urls) => { + blockPostPostURL = urls.blockPostPostURL; + }); + + // Set address fields (Addr 1 and City) as required + cy.getListId('10up').then((listId) => { + cy.updateMergeFieldByTag(listId, 'ADDRESS', { required: true }).then(() => { + cy.selectList('10up'); // Refresh list in WordPress + }); + }); + + // Test validation without JS to ensure error handling mechanism for all scenarios + cy.setJavaScriptOption(false); + }); + + after(() => { + // Cleanup: Reset address fields to optional + cy.getListId('10up').then((listId) => { + cy.updateMergeFieldByTag(listId, 'ADDRESS', { required: false }).then(() => { + cy.selectList('10up'); // Refresh list in WordPress + }); + }); + cy.setJavaScriptOption(true); + }); + + it('Valid addresses submit', () => { + validAddresses.forEach((address) => { + cy.visit(blockPostPostURL); + + const email = generateRandomEmail('validemail'); + cy.get('#mc_mv_EMAIL').type(email); + cy.get('#mc_mv_ADDRESS-addr1').clear().type(address.addr1); + cy.get('#mc_mv_ADDRESS-city').clear().type(address.city); + cy.get('#mc_mv_ADDRESS-state').clear().type(address.state); + cy.get('#mc_mv_ADDRESS-zip').type(address.zip); + cy.get('#mc_mv_ADDRESS-country').type(address.country); + cy.submitFormAndVerifyWPSuccess(); + + // Delete contact to clean up + cy.deleteContactFromList(email); + }); + }); + + it('Invalid addresses fail validation and display error message', () => { + invalidAddresses.forEach((address) => { + cy.visit(blockPostPostURL); + + const email = generateRandomEmail('invalidemail'); + cy.get('#mc_mv_EMAIL').type(email); + + if (address.addr1 !== '') { + cy.get('#mc_mv_ADDRESS-addr1').clear().type(address.addr1); + } + if (address.city !== '') { + cy.get('#mc_mv_ADDRESS-city').clear().type(address.city); + } + + cy.submitFormAndVerifyError(); + + if (!address.addr1) { + cy.get('.mc_error_msg').contains('Address: Please enter a complete address'); + } + if (!address.city) { + cy.get('.mc_error_msg').contains('Address: Please enter a complete address'); + } + }); + }); +}); diff --git a/tests/cypress/e2e/validation/email.test.js b/tests/cypress/e2e/validation/email.test.js new file mode 100644 index 00000000..9a88f32d --- /dev/null +++ b/tests/cypress/e2e/validation/email.test.js @@ -0,0 +1,96 @@ +/* eslint-disable no-undef */ + +/** + * NOTE: We aren't verifying successful submission in the Mailchimp account for this test suite. + * Those assertions are covered in the submission tests. + * + * Hypothetically, it's possible that validation passes WP, but fails in Mailchimp. + * + * However, the response the API receives comes directly from Mailchimp and is displayed + * on the FE. So, if the form is submitted successfully, the submission should be in Mailchimp. + */ +describe('General merge field validation', () => { + let blockPostPostURL; + const invalidEmailErrorRegex = /please.*valid email/i; // please...valid email + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + ({ blockPostPostURL } = urls); + }); + + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + // Set all merge fields to not required in the Mailchimp test user account + cy.setMergeFieldsRequired(false); + + // Disable all merge fields + cy.toggleMergeFields('uncheck'); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Test validation without JS to ensure error handling mechanism for all scenarios + cy.setJavaScriptOption(false); + }); + + after(() => { + cy.setJavaScriptOption(true); + }); + + it('Invalid email addresses fail validation', () => { + cy.visit(blockPostPostURL); + + // Ensure the form exists + cy.get('#mc_signup').should('exist'); + cy.get('#mc_mv_EMAIL').should('exist'); + cy.get('#mc_signup_submit').should('exist'); + + // Email assertions + cy.get('#mc_mv_EMAIL').clear(); // No email + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + + cy.get('#mc_mv_EMAIL').clear().type('user@'); // Missing domain + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + cy.get('#mc_mv_EMAIL').clear().type('@example.com'); // Missing username + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + cy.get('#mc_mv_EMAIL').clear().type('userexample.com'); // Missing '@' symbol + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + cy.get('#mc_mv_EMAIL').clear().type('user..name@example.com'); // Consecutive dots + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + cy.get('#mc_mv_EMAIL').clear().type('user!#%&*{}@example.com'); // Invalid characters + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + cy.get('#mc_mv_EMAIL').clear().type('user@example'); // Missing top-level domain + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + cy.get('#mc_mv_EMAIL').clear().type('user@-example.com'); // Domain starting with dash + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + // TODO: Mailchimp accepts this. Is this a bug? + // cy.get('#mc_mv_EMAIL').clear().type('user@example-.com'); // Domain ending with dash + // cy.submitFormAndVerifyError(); + // cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + cy.get('#mc_mv_EMAIL').clear().type('"user@example.com'); // Unclosed quoted string + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + + // Test exceeding maximum email length + let longEmail = 'a'.repeat(245) + '@example.com'; + cy.get('#mc_mv_EMAIL').clear().type(longEmail); + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + }); +}); diff --git a/tests/cypress/e2e/validation/us-phone.test.js b/tests/cypress/e2e/validation/us-phone.test.js new file mode 100644 index 00000000..5cb4290e --- /dev/null +++ b/tests/cypress/e2e/validation/us-phone.test.js @@ -0,0 +1,115 @@ +/* eslint-disable no-undef */ +import { generateRandomEmail } from '../../support/functions/utility'; + +/** + * Test Suite for Multi-Input Phone Number Validation + * Handles both JavaScript-enabled and disabled scenarios for length and format validation. + */ +// TODO: BUG: Skipping for now because when a US phone number is selected in the Mailchimp account, but +// not present on the webform there will always be a fatal error. There is a fix pending for 1.7.0. +// TODO: Skipping for now because the Mailchimp API does not allow changing the format for a phone merge +// field to the US style +describe.skip('US Multi-Input Phone Number Validation', () => { + let blockPostPostURL; + + const validPhones = [ + { area: '123', detail1: '456', detail2: '7890' }, + { area: '987', detail1: '654', detail2: '3210' }, + ]; + const invalidPhones = [ + { area: '123', detail1: '456', detail2: '78a0' }, + { area: '123', detail1: '45!', detail2: '7890' }, + ]; + const tooShortPhones = [ + { area: '12', detail1: '456', detail2: '789' }, + { area: '', detail1: '45', detail2: '7890' }, + ]; + const tooLongPhones = [ + { area: '1234', detail1: '567', detail2: '890' }, + { area: '123', detail1: '4567', detail2: '8901' }, + ]; + + before(() => { + cy.login(); + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + cy.fixture('postUrls').then((urls) => { + ({ blockPostPostURL } = urls); + }); + + cy.getListId('10up').then((listId) => { + cy.updateMergeFieldByTag(listId, 'PHONE', { + required: true, + options: { phone_format: 'US' }, + }).then(() => { + cy.selectList('10up'); + }); + }); + + // Test validation without JS to ensure error handling mechanism for all scenarios + cy.setJavaScriptOption(false); + }); + + after(() => { + cy.getListId('10up').then((listId) => { + cy.updateMergeFieldByTag(listId, 'PHONE', { + required: false, + options: { phone_format: 'none' }, + }); + }); + cy.selectList('10up'); + cy.setJavaScriptOption(true); + }); + + function fillPhoneInputs(phone) { + cy.get('#mc_mv_PHONE-area').clear().type(phone.area); + cy.get('#mc_mv_PHONE-detail1').clear().type(phone.detail1); + cy.get('#mc_mv_PHONE-detail2').clear().type(phone.detail2); + } + + it('Valid phone numbers', () => { + cy.visit(blockPostPostURL); + + validPhones.forEach((phone) => { + const email = generateRandomEmail('validphone'); + cy.get('#mc_mv_EMAIL').type(email); + fillPhoneInputs(phone); + cy.submitFormAndVerifyWPSuccess(); + + // Delete contact to clean up + cy.deleteContactFromList(email); + }); + }); + + it('Invalid phone numbers', () => { + cy.visit(blockPostPostURL); + + invalidPhones.forEach((phone) => { + const email = generateRandomEmail('invalidphone'); + cy.get('#mc_mv_EMAIL').type(email); + fillPhoneInputs(phone); + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains('must consist of only numbers'); + }); + }); + + it('Phone length validation', () => { + cy.visit(blockPostPostURL); + + tooShortPhones.forEach((phone) => { + const email = generateRandomEmail('shortphone'); + cy.get('#mc_mv_EMAIL').type(email); + fillPhoneInputs(phone); + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains('Phone number is too short'); + }); + + tooLongPhones.forEach((phone) => { + const email = generateRandomEmail('longphone'); + cy.get('#mc_mv_EMAIL').type(email); + fillPhoneInputs(phone); + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains('Phone number is too long'); + }); + }); +}); diff --git a/tests/cypress/e2e/validation/validate-required-fields.test.js b/tests/cypress/e2e/validation/validate-required-fields.test.js new file mode 100644 index 00000000..e42612c0 --- /dev/null +++ b/tests/cypress/e2e/validation/validate-required-fields.test.js @@ -0,0 +1,115 @@ +/* eslint-disable no-undef */ +import { generateRandomEmail } from '../../support/functions/utility'; + +describe('Validate required fields', () => { + let blockPostPostURL; + const email = generateRandomEmail('testemail-neversubmitted'); + + // (almost) the same in the WP admin as on the FE + const requiredFields = [ + { selector: '#mc_mv_FNAME', errorMessage: 'First Name:', input: 'Test' }, + { selector: '#mc_mv_LNAME', errorMessage: 'Last Name:', input: 'User' }, + { selector: '#mc_mv_ADDRESS-addr1', errorMessage: 'Address:', input: '123 Fake St.' }, // Address has sub fields on the FE form + { selector: '#mc_mv_ADDRESS-city', errorMessage: 'Address:', input: 'Nashville' }, // Address has sub fields on the FE form + { selector: '#mc_mv_ADDRESS-state', errorMessage: 'Address:', input: 'TN' }, // Address has sub fields on the FE form + { selector: '#mc_mv_ADDRESS-zip', errorMessage: 'Address:', input: '12345' }, // Address has sub fields on the FE form + { selector: '#mc_mv_BIRTHDAY', errorMessage: 'Birthday:', input: '01/10' }, + { selector: '#mc_mv_COMPANY', errorMessage: 'Company:', input: '10up' }, + { selector: '#mc_mv_PHONE', errorMessage: 'Phone Number:', input: '555-555-5555' }, + { selector: '#mc_mv_MMERGE8', errorMessage: 'Date:', input: '01/01/2030' }, + { selector: '#mc_mv_MMERGE9', errorMessage: 'Zip Code:', input: '12345' }, + { selector: '#mc_mv_MMERGE10', errorMessage: 'Website:', input: 'https://10up.com' }, + { + selector: '#mc_mv_MMERGE11', + errorMessage: 'Image:', + input: 'https://10up.com/wp-content/themes/10up-sept2016/assets/img/icon-strategy.png', + }, + ]; + + const requiredSelectFields = [ + // Country is selected by default so no need to test this validation + // { selector: '#mc_mv_ADDRESS-country', errorMessage: 'Address:', input: 'USA' }, // Address has sub fields on the FE form + { selector: '#mc_mv_MMERGE7', errorMessage: 'Choose one:', input: 'First Choice' }, + ]; + + const requiredCheckboxFields = [ + { selector: '#mc_mv_MMERGE6_0', errorMessage: 'Choose one:', input: 'First Choice' }, + ]; + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then(({ blockPostPostURL: url }) => { + blockPostPostURL = url; + }); + + cy.login(); // WordPress login + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + // Set all merge fields to required in the Mailchimp test user account + cy.setMergeFieldsRequired(true); + + // Test validation without JS to ensure error handling mechanism for all scenarios + cy.setJavaScriptOption(false); + }); + + after(() => { + // I don't know why we need to login again, but we do + cy.login(); // WordPress login + + // Cleanup: Set all merge fields to not required in the Mailchimp test user account + cy.setMergeFieldsRequired(false); + + // Cleanup: Uncheck all optional merge fields + cy.toggleMergeFields('uncheck'); + + // Cleanup: Re-enable JS support + cy.setJavaScriptOption(true); + }); + + // TODO: Validation errors clear the entire form. We should fix this. + // We could also significantly reduce the time this test takes by fixing this bug. + function fillOutAllFields() { + cy.get('#mc_mv_EMAIL').clear().type(email); // Email is always required + + requiredFields.forEach((field) => { + cy.get(field.selector).clear().type(field.input); + cy.get('body').click(0, 0); // Click outside the field to clear the datepicker modal + }); + + requiredSelectFields.forEach((field) => { + cy.get(field.selector).select(field.input); + cy.get('body').click(0, 0); // Click outside the field to clear the datepicker modal + }); + + requiredCheckboxFields.forEach((field) => { + cy.get(field.selector).check(); + cy.get('body').click(0, 0); // Click outside the field to clear the datepicker modal + }); + } + + // TODO: Test just takes too long to run + it('ensures that a required field can not be empty', () => { + cy.visit(blockPostPostURL); + + // Ensure the form exists + cy.get('#mc_signup').should('exist'); + + // Test validation for each required field + requiredFields.forEach((field) => { + // Fill out entire form everytime so we can narrow tests to one input at a time + fillOutAllFields(); + + // Submit the form without input to trigger validation + cy.get(field.selector).clear(); // Ensure field is empty + cy.get('body').click(0, 0); // Click outside the field to clear the datepicker modal + cy.get('#mc_signup_submit').click(); + + // Assert the error message is displayed + cy.get('.mc_error_msg').should('exist'); + cy.get('.mc_error_msg').should('include.text', field.errorMessage); + + // Fill in the field + cy.get(field.selector).type(field.input); + }); + }); +}); diff --git a/tests/cypress/e2e/validation/validate-unrequired-fields.test.js b/tests/cypress/e2e/validation/validate-unrequired-fields.test.js new file mode 100644 index 00000000..98f3426c --- /dev/null +++ b/tests/cypress/e2e/validation/validate-unrequired-fields.test.js @@ -0,0 +1,92 @@ +/* eslint-disable no-undef */ +import { generateRandomEmail } from '../../support/functions/utility'; + +describe('Validate unrequired fields', () => { + let shortcodePostURL; + let blockPostPostURL; + + before(() => { + // Load the post URLs from the JSON file + cy.fixture('postUrls').then((urls) => { + ({ shortcodePostURL, blockPostPostURL } = urls); + }); + + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + // Set all merge fields to not required in the Mailchimp test user account + cy.setMergeFieldsRequired(false); + + // Enable all merge fields + cy.toggleMergeFields('check'); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Test validation without JS to ensure error handling mechanism for all scenarios + cy.setJavaScriptOption(false); + }); + + after(() => { + // I don't know why we need to login again, but we do + cy.login(); // WordPress login + + // Cleanup + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.toggleMergeFields('uncheck'); // TODO: Do I need to uncheck all merge fields? + + cy.setJavaScriptOption(true); + }); + + it('Unrequired fields can be submitted while blank', () => { + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + + cy.get('#mc_signup').should('exist'); + cy.get('#mc_mv_EMAIL').should('exist'); + cy.get('#mc_signup_submit').should('exist'); + + // Optional merge fields + cy.get('#mc_mv_FNAME').should('exist'); + cy.get('#mc_mv_LNAME').should('exist'); + cy.get('#mc_mv_ADDRESS-addr1').should('exist'); // Address line 1 + cy.get('#mc_mv_ADDRESS-addr2').should('exist'); // Address line 2 + cy.get('#mc_mv_ADDRESS-city').should('exist'); // City + cy.get('#mc_mv_ADDRESS-state').should('exist'); // State + cy.get('#mc_mv_ADDRESS-zip').should('exist'); // ZIP code + cy.get('#mc_mv_ADDRESS-country').should('exist'); // Country + cy.get('#mc_mv_BIRTHDAY').should('exist'); + cy.get('#mc_mv_COMPANY').should('exist'); + cy.get('#mc_mv_PHONE').should('exist'); + + // Validation assertions + + // Email is required + const email = generateRandomEmail('unrequired-validation-test'); + cy.get('#mc_mv_EMAIL').type(email); + + // Step 6: Verify that the form was submitted successfully + cy.submitFormAndVerifyWPSuccess(); + + // Step 7: Verify that the contact was added to the Mailchimp account via the Mailchimp API + cy.verifyContactInMailchimp(email); + + // Step 8: Cleanup and delete contact + cy.deleteContactFromList(email); + + /** + * Phone Number - Handled in /validation/us-phone.test.js + */ + + /** + * Address - Handled in /validation/address.test.js + */ + + /** + * Remaining merge fields + */ + // Test birthday - no validation + // Test company - no validation + // Test first name - no validation + // Test last name - no validation + }); + }); +}); diff --git a/tests/cypress/fixtures/mergeFields.json b/tests/cypress/fixtures/mergeFields.json new file mode 100644 index 00000000..8871858f --- /dev/null +++ b/tests/cypress/fixtures/mergeFields.json @@ -0,0 +1,8 @@ +{ + "FNAME": "#mc_mv_FNAME", + "LNAME": "#mc_mv_LNAME", + "ADDRESS": "#mc_mv_ADDRESS", + "BIRTHDAY": "#mc_mv_BIRTHDAY", + "COMPANY": "#mc_mv_COMPANY", + "PHONE": "#mc_mv_PHONE" +} \ No newline at end of file diff --git a/tests/cypress/fixtures/postUrls.json b/tests/cypress/fixtures/postUrls.json new file mode 100644 index 00000000..42a4e59c --- /dev/null +++ b/tests/cypress/fixtures/postUrls.json @@ -0,0 +1,4 @@ +{ + "shortcodePostURL": "/mailchimp-signup-form-shortcode", + "blockPostPostURL": "/mailchimp-signup-form-block" +} \ No newline at end of file diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 8d898235..385cdc9a 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -25,26 +25,9 @@ // -- 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')); -}); +import './commands/popup'; +import './commands/utility'; +import './commands/settings'; +import './commands/mailchimpLogin'; +import './commands/mailchimpApi'; +import './commands/submission'; diff --git a/tests/cypress/support/commands/mailchimpApi.js b/tests/cypress/support/commands/mailchimpApi.js new file mode 100644 index 00000000..c09c009a --- /dev/null +++ b/tests/cypress/support/commands/mailchimpApi.js @@ -0,0 +1,315 @@ +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 list ID from a list name + */ +Cypress.Commands.add('getListId', getListId); +async function getListId(listName) { + const lists = await getAllLists(); + const list = lists.find((list) => list.name === listName); + return list.id; +} + +/** + * 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, status = null) { + let members = []; + let offset = 0; + const count = 100; // Number of members to fetch per request (Mailchimp's limit is usually 1000 per page) + + while (true) { + const options = { + count, + offset, + }; + + // Add status filter if provided + if (status) { + options.status = status; + } + + const response = await mailchimp.lists.getListMembersInfo(listId, options); + + members = members.concat(response.members); + + // Break the loop if we've fetched all members + if (members.length >= response.total_items) { + break; + } + + // Increment the offset for the next page + offset += count; + } + + return members; +} + +/** + * Retrieve a contact's details from a Mailchimp list. + * + * This function fetches the details of a specific contact from a Mailchimp list + * using the MD5 hash of the email address. If the contact is not found, it returns `null`. + * Logs and rethrows unexpected errors for visibility. + * + * @param {string} listId - The Mailchimp list ID to search within. + * @param {string} email - The email address of the contact to retrieve. + * @returns {Promise} - A promise that resolves with the contact's details + * if found, or `null` if the contact does not exist. + * + * @throws {Error} - Throws an error for unexpected failures (non-404 errors). + * + * Example: + * cy.getContact(listId, 'user@example.com').then((contact) => { + * if (contact) { + * console.log('Contact found:', contact); + * } else { + * console.log('Contact not found.'); + * } + * }); + */ +async function getContact(email, listId) { + try { + // Generate MD5 hash of the lowercase email address + const emailHash = require('crypto') + .createHash('md5') + .update(email.toLowerCase()) + .digest('hex'); + + // Fetch and return the contact details + return await mailchimp.lists.getListMember(listId, emailHash); + } catch (error) { + if (error.response?.status === 404) { + // Return null if the contact is not found + return null; + } + // Log and rethrow other errors for visibility + console.error('Error fetching contact:', error.response?.body || error.message); + throw error; + } +} + +/** + * Retrieve a contact's details from a Mailchimp list by its name. + * + * @param {string} email - The email address of the contact to retrieve. + * @param {string} listName - The name of the Mailchimp list (default is '10up'). + * @returns {Promise} - A promise that resolves to the contact details if found, or `null` if not found. + */ +Cypress.Commands.add('getContactFromList', getContactFromList); +function getContactFromList(email, listName = '10up') { + return cy.getListId(listName).then((listId) => { + return cy.wrap(getContact(email, listId)); // Wrap the promise to work with Cypress chaining + }); +} + +/** + * Set all merge fields to required in the Mailchimp test user account + * + * TODO: Configuration this to use the batch endpoint. Is the /batch endpoint worth the lift? + * https://mailchimp.com/developer/marketing/guides/run-async-requests-batch-endpoint/#make-a-batch-operations-request + * + * @param {string} listId - The Mailchimp list ID + * @param {object} data - The data to update the merge fields with - Docs: https://mailchimp.com/developer/marketing/api/list-merges/update-merge-field/ + * @returns {Promise} - A promise that resolves when all merge fields are updated + */ +Cypress.Commands.add('updateMergeFieldsByList', updateMergeFieldsByList); +async function updateMergeFieldsByList(listId, data) { + const mergeFields = await getMergeFields(listId); + const updatedMergeFields = mergeFields.map((field) => { + return updateMergeField(listId, field.merge_id, field.name, data); + }); + + return await Promise.all(updatedMergeFields); +} + +/** + * Update merge field by tag + * + * @param {string} listId - The Mailchimp list ID + * @param {string} tag - The merge field tag (e.g. "FNAME", "PHONE", etc.) + * @param {object} data - The data to update the merge field with - Docs: https://mailchimp.com/developer/marketing/api/list-merges/update-merge-field/ + */ +Cypress.Commands.add('updateMergeFieldByTag', updateMergeFieldByTag); +async function updateMergeFieldByTag(listId, tag, data) { + const mergeFields = await getMergeFields(listId); + const field = mergeFields.find((field) => field.tag === tag); // Filter what we want by tag + const response = await updateMergeField(listId, field.merge_id, field.name, data); + console.log('Updated merge field:', response); + return response; +} + + +/** + * Get all merge fields for a list + * + * @param {string} listId - The Mailchimp list ID + * @returns {Promise} - A promise that resolves with all merge fields for the list + */ +Cypress.Commands.add('getMergeFields', async (listId) => { + return getMergeFields(listId); +}); + +/** + * Get all merge fields for a list + * + * Mailchimp paginates merge fields + * @param {string} listId - The Mailchimp list ID + * @returns {Promise} - A promise that resolves with all merge fields for the list + */ +async function getMergeFields(listId) { + let mergeFields = []; + let offset = 0; + const count = 100; // Number of fields to fetch per request + + while (true) { + const response = await mailchimp.lists.getListMergeFields(listId, { + count, + offset, + }); + + mergeFields = mergeFields.concat(response.merge_fields); + + // Break the loop if we've fetched all the merge fields + if (mergeFields.length >= response.total_items) { + break; + } + + // Increment the offset for the next batch + offset += count; + } + + return mergeFields; +} + +/** + * Updates merge fields for a list + */ +async function updateMergeField(listId, mergeId, name, data) { + return await mailchimp.lists.updateListMergeField( + listId, + mergeId, + { + ...data, + name: name, + } + ); +} + +/** + * Wrapper function to delete a contact specifically from the "10up" Mailchimp list. + * + * This function wraps the generic `deleteContact` function and automatically + * retrieves the list ID for the "10up" list. It simplifies the process of + * deleting contacts from this specific list by removing the need to manually + * provide the list ID. + * + * @param {string} email - The email address of the contact to delete + */ +Cypress.Commands.add('deleteContactFromList', deleteContactFromList); +function deleteContactFromList(email, listName = '10up') { + cy.getListId(listName).then((listId) => { + deleteContact(listId, email); + }); +} + +/** + * Delete a contact from a Mailchimp list + * + * @param {string} listId - The Mailchimp list ID + * @param {string} email - The email address of the contact to delete + * @returns {Promise} - A promise that resolves when the contact is successfully deleted + * + * This function deletes a contact from the specified Mailchimp list by using the MD5 hash + * of the lowercase email address. Mailchimp requires this hashed value to uniquely identify + * contacts. + */ +async function deleteContact(listId, email) { + try { + // Generate MD5 hash of the lowercase email address + const emailHash = require('crypto') + .createHash('md5') + .update(email.toLowerCase()) + .digest('hex'); + + // Delete the contact from the list + await mailchimp.lists.deleteListMember(listId, emailHash); + console.log(`Successfully deleted contact: ${email}`); + } catch (error) { + console.error('Error deleting contact:', error.response ? error.response.body : error.message); + } +} + +/** + * Subscribe an email to a Mailchimp list + * + * @param {string} listId - The Mailchimp list ID + * @param {string} email - The email address to subscribe + * @param {object} mergeFields - (Optional) Merge fields (e.g., { FNAME: 'John', LNAME: 'Doe' }) + * @returns {Promise} - A promise that resolves when the subscription is successful + */ +Cypress.Commands.add('subscribeToList', subscribeToList); +async function subscribeToList(listId, email, mergeFields = {}) { + try { + // Subscribe the contact to the list + const response = await mailchimp.lists.addListMember(listId, { + email_address: email, + status: 'subscribed', // 'subscribed', 'unsubscribed', 'pending', or 'cleaned' + merge_fields: mergeFields, // Optional merge fields for personalization + }); + + console.log(`Successfully subscribed ${email} to list ${listId}`); + return response; + } catch (error) { + console.error('Error subscribing email:', error.response ? error.response.body : error.message); + throw new Error(`Failed to subscribe ${email} to list ${listId}`); + } +} + +/** + * Subscribe an email to a Mailchimp list by its name. + * + * @param {string} email - The email address to subscribe. + * @param {string} listName - The name of the Mailchimp list (default is '10up'). + */ +Cypress.Commands.add('subscribeToListByName', subscribeToListByName); +function subscribeToListByName(email, listName = '10up') { + cy.getListId(listName).then((listId) => { + cy.subscribeToList(listId, email); + console.log(`Successfully subscribed ${email} to list ${listName}`); + }); +} 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..64c221dc --- /dev/null +++ b/tests/cypress/support/commands/mailchimpLogin.js @@ -0,0 +1,86 @@ +/** + * 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', () => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + // 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(); + cy.get('.mc-user h3').contains('Logged in as: '); + } else { + cy.log('Already logged into Mailchimp account'); + } + }); +}); 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/settings.js b/tests/cypress/support/commands/settings.js new file mode 100644 index 00000000..f235f580 --- /dev/null +++ b/tests/cypress/support/commands/settings.js @@ -0,0 +1,153 @@ +/** + * Custom Cypress command to select a Mailchimp list in the WordPress admin settings. + * + * This command navigates to the Mailchimp plugin settings page in the WordPress admin, + * selects a specified list from the dropdown, and submits the form to update the settings. + * It is useful for setting up the test environment with the correct Mailchimp list. + * + * @param {string} listName - The name of the Mailchimp list to select. + * + * @example + * // Select a Mailchimp list named "10up List" + * cy.selectList('10up List'); + */ +Cypress.Commands.add('selectList', (listName) => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_list_id').select(listName, {force: true}); + cy.get('input[value="Update List"]').click(); +}); + +/** + * Custom Cypress command to enable or disable the JavaScript option in Mailchimp WordPress admin settings. + * + * This command visits the Mailchimp plugin settings page in the WordPress admin, + * toggles the "Use JavaScript" option based on the specified parameter, and + * updates the settings by submitting the form. It is helpful for testing scenarios + * that depend on JavaScript behavior in the plugin. + * + * @param {boolean} enabled - A flag to enable (`true`) or disable (`false`) the JavaScript option. + * + * @example + * // Enable the JavaScript option + * cy.setJavaScriptOption(true); + * + * @example + * // Disable the JavaScript option + * cy.setJavaScriptOption(false); + */ +Cypress.Commands.add('setJavaScriptOption', setJavaScriptOption); +function setJavaScriptOption(enabled) { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + if (enabled) { + cy.get('#mc_use_javascript').check(); + } else { + cy.get('#mc_use_javascript').uncheck(); + } + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); +} + +/** + * Custom Cypress command to enable or disable the Double Opt-In option in Mailchimp WordPress admin settings. + * + * This command visits the Mailchimp plugin settings page in the WordPress admin, + * toggles the "Double Opt-In" option based on the specified parameter, and + * updates the settings by submitting the form. It is useful for testing scenarios + * that require configuring the Double Opt-In behavior in the plugin. + * + * @param {boolean} enabled - A flag to enable (`true`) or disable (`false`) the Double Opt-In option. + * + * @example + * // Enable the Double Opt-In option + * cy.setDoubleOptInOption(true); + * + * @example + * // Disable the Double Opt-In option + * cy.setDoubleOptInOption(false); + */ +Cypress.Commands.add('setDoubleOptInOption', setDoubleOptInOption); +function setDoubleOptInOption(enabled) { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + if (enabled) { + cy.get('#mc_double_optin').check(); + } else { + cy.get('#mc_double_optin').uncheck(); + } + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); +} + +Cypress.Commands.add('setSettingsOption', setSettingsOption); +function setSettingsOption(selector, enabled) { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + if (enabled) { + cy.get(selector).check(); + } else { + cy.get(selector).uncheck(); + } + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); +} + +/** + * Custom Cypress command to enable or disable Mailchimp merge fields in the WordPress admin settings. + * + * This command visits the Mailchimp plugin settings page in the WordPress admin + * and toggles the visibility of specified merge fields (e.g., First Name, Last Name, etc.) + * based on the provided action. It ensures the specified fields exist before performing + * the action and submits the form to save the changes. + * + * @param {string} action - The action to perform on each merge field, either "check" or "uncheck". + * Use "check" to enable the fields and "uncheck" to disable them. + * + * @example + * // Enable all merge fields + * cy.toggleMergeFields('check'); + * + * @example + * // Disable all merge fields + * cy.toggleMergeFields('uncheck'); + */ +Cypress.Commands.add('toggleMergeFields', toggleMergeFields); +function toggleMergeFields(action) { + // Load the fields from the fixture + cy.fixture('mergeFields').then((fields) => { + const mergeFields = Object.values(fields); // Extract field selectors as an array + + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + + mergeFields.forEach((field) => { + cy.get(field).should('exist')[action](); + }); + + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + }); +} + +/** + * Custom Cypress command to set all Mailchimp merge fields as required or optional in the WordPress admin settings. + * + * This command updates the merge fields for a specified Mailchimp list, setting their "required" status + * in the Mailchimp account. It ensures the WordPress plugin reflects these changes by re-selecting the list + * in the plugin settings. + * + * @param {boolean} required - A flag to set the merge fields as required (`true`) or optional (`false`). + * @param {string} [listName='10up'] - The name of the Mailchimp list for which the merge fields will be updated. + * + * @example + * // Set all merge fields to required + * cy.setMergeFieldsRequired(true); + * + * @example + * // Set all merge fields to optional + * cy.setMergeFieldsRequired(false); + * + * @example + * // Set merge fields for a specific list + * cy.setMergeFieldsRequired(true, 'Custom List'); + */ +Cypress.Commands.add('setMergeFieldsRequired', (required, listName = '10up') => { + // Set all merge fields to required in the Mailchimp test user account + cy.getListId(listName).then((listId) => { + cy.updateMergeFieldsByList(listId, { required: required }).then(() => { + cy.selectList(listName); // Ensure list is selected, refreshes Mailchimp data with WP + }); + }); +}) \ No newline at end of file diff --git a/tests/cypress/support/commands/submission.js b/tests/cypress/support/commands/submission.js new file mode 100644 index 00000000..0f792603 --- /dev/null +++ b/tests/cypress/support/commands/submission.js @@ -0,0 +1,53 @@ +/** + * Custom command to submit a form and verify that the submission was successful + */ +Cypress.Commands.add('submitFormAndVerifyWPSuccess', () => { + // Submit the form + cy.get('#mc_signup_submit').click(); + + // Verify that the form was submitted successfully + cy.get('.mc_success_msg').should('exist'); +}); + +/** + * Submit form and verify error + */ +Cypress.Commands.add('submitFormAndVerifyError', () => { + // Submit the form + cy.get('#mc_signup_submit').click(); + + // Verify that the form submission failed + cy.get('.mc_error_msg').should('exist'); +}); + +/** + * Custom command to verify that a contact was added to a specified list in Mailchimp + */ +Cypress.Commands.add('verifyContactInMailchimp', (email, listName = '10up', status = null) => { + // Step 1: Get the list ID for the specified list name + cy.getListId(listName).then((listId) => { + // Step 2: Retrieve the contacts from the specified list + cy.getContactsFromAList(listId, status).then((contacts) => { + // Log the contacts for debugging + console.log(contacts) + + // Step 3: Verify that the contact with the provided email exists in the list + const contact = contacts.find((c) => c.email_address === email); + expect(contact).to.exist; + cy.wrap(contact); // Wrap the contact to allow further chaining + }); + }); +}); + +/** + * Custom command to verify that a contact's status matches the expected status. + * + * @param {Object} contact - The contact object to verify. + * @param {string} status - The expected status to compare against. + * + * @example + * cy.verifyContactStatus(contact, 'subscribed'); + */ +Cypress.Commands.add('verifyContactStatus', (contact, status) => { + expect(contact.status).to.equal(status); +}); diff --git a/tests/cypress/support/commands/utility.js b/tests/cypress/support/commands/utility.js new file mode 100644 index 00000000..08ce1f7e --- /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.'); +}); diff --git a/tests/cypress/support/functions/utility.js b/tests/cypress/support/functions/utility.js new file mode 100644 index 00000000..72799a01 --- /dev/null +++ b/tests/cypress/support/functions/utility.js @@ -0,0 +1,41 @@ +/** + * Utility Functions for Cypress Tests + * + * This file contains miscellaneous utility JavaScript functions + * intended for use in Cypress test cases. The functions in this file + * are designed to be: + * + * - **Synchronous**: These functions should execute immediately and return values directly, avoiding Cypress's promise-like chaining. + * - **Standalone**: These are plain JavaScript functions, not Cypress commands, to provide flexibility and simplicity. + * - **Reusable**: Utility functions in this file should be reusable across different test cases without modification. + * + * Guidelines: + * 1. Do not wrap these functions in Cypress commands or use `cy.*` calls within them. + * 2. Ensure all functions remain synchronous to avoid unintended behavior in Cypress's execution flow. + * 3. Functions should follow proper documentation for easy understanding and maintenance. + * + * Example Usage (Adjust for your file structure): + * ``` + * import { generateRandomEmail } from '../../support/functions/utility'; + * + * const email = generateRandomEmail('test'); + * cy.get('#email').type(email); + * ``` + */ + +/** + * Generates a random email address using the provided prefix. + * + * NOTE: Keeping as JS function instead of Cypress command to avoid promises + * + * @param {string} prefix - The prefix to prepend to the generated email address. + * @returns {string} A unique email address in the format: `${prefix}-unixtimestamp-${Date.now()}@10up.com`. + * + * @example + * // Returns "test-unixtimestamp-1672531200000@10up.com" + * const email = generateRandomEmail('test'); + * console.log(email); + */ +export function generateRandomEmail(prefix) { + return `${prefix}-unixtimestamp-${Date.now()}@10up.com`; +} diff --git a/tests/cypress/support/index.js b/tests/cypress/support/index.js index 5652037e..a0364cee 100644 --- a/tests/cypress/support/index.js +++ b/tests/cypress/support/index.js @@ -14,10 +14,35 @@ // *********************************************************** import '@10up/cypress-wp-utils'; - -// Import commands.js using ES2015 syntax: import './commands'; +// TODO: Initialize tests from a blank state +// TODO: Wipe WP data related to a users options +// TODO: Delete all contacts in a users Mailchimp account +// TODO: Include all merge fields as "Visible" in the users Mailchimp account +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!'); + + // Default settings for tests + cy.login(); // WP + cy.mailchimpLoginIfNotAlreadyLoggedIn(); + + cy.selectList('10up'); + + cy.setDoubleOptInOption(false); + cy.setJavaScriptOption(true); + cy.setSettingsOption('#mc_update_existing', false); + + // Merge fields + cy.setMergeFieldsRequired(false); // No merge fields are required + cy.toggleMergeFields('uncheck'); // Start without merge fields + + cy.log('Default testing options set!'); +}); + beforeEach( () => { cy.session( 'login', cy.login, { cacheAcrossSpecs: true,