From 895ae4a6c47145f10ce4fda8f02f7171bd4473ee Mon Sep 17 00:00:00 2001 From: "Aman Kumar [SSW]" <71385247+amankumarrr@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:07:45 +1100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=9FUI=20testing=20-=20Submit=20an=20en?= =?UTF-8?q?quiry=20form=20(#1464)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * UI Testing - adding test cases * Running example for create lead * Refactoring the code * Adding extension for playwright * disabling prettier log error * Adding env variable for target_URL for testing * Adding key reference in PR and main Slot's config * Updating yarn.lock conflict * Removing test-examples from the project * Flow - Adding enquiry flow to run the tests * Updating the env variable * Adding trailing double quote * Updating secret with env variables for test run * Adding mask to the secret * Adding workflow_dispatch variable * Refactoring code * Changing the retention day * Removing the pull request branches * Matt's Feedback * Adding doc for running it on local * Updating host url to prod * Renamed the variable name * Update create-lead * removing Id from util button * Matt's feedback 2 * Update pages/api/create-lead.ts Co-authored-by: Matt Wicks [SSW] * Update pages/api/create-lead.ts Co-authored-by: Matt Wicks [SSW] * Update components/bookingButton/bookingButton.tsx Co-authored-by: Matt Wicks [SSW] * Update components/bookingButton/bookingButton.tsx Co-authored-by: Matt Wicks [SSW] * Updated filename to make it more descriptive * Update .github/workflows/weekly-ui-tests.yml * ♻️ general cleanup - ui tests - renamed file to match component being tests - ui tests - dry'd up code, preferred placeholders over attribute selectors where possible - changed name of secret to bypass recaptcha (prefixed with recaptcha) - playwright config - base url for tests should be set at this level - readme - updated to run the ui tests the same way as the github action - env file - updated example as it was out of sync with real settings * lint fixes --------- Co-authored-by: Matt Wicks [SSW] Co-authored-by: Matt Wicks --- .env.example | 20 ++++-- .github/workflows/weekly-ui-tests.yml | 79 ++++++++++++++++++++++ .gitignore | 4 ++ .vscode/extensions.json | 13 ++-- README.md | 6 ++ components/bookingButton/bookingButton.tsx | 2 +- components/successToast/successToast.tsx | 2 +- infra/appSerivce-create-slot.bicep | 4 ++ infra/appService.bicep | 4 ++ package.json | 2 + pages/api/create-lead.ts | 24 ++++++- pages/consulting/[filename].tsx | 4 +- playwright.config.ts | 79 ++++++++++++++++++++++ ui-tests/components/bookingForm.spec.ts | 41 +++++++++++ yarn.lock | 48 ++++++++++++- 15 files changed, 314 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/weekly-ui-tests.yml create mode 100644 playwright.config.ts create mode 100644 ui-tests/components/bookingForm.spec.ts diff --git a/.env.example b/.env.example index 30580b887d..a14a1727e6 100644 --- a/.env.example +++ b/.env.example @@ -8,22 +8,32 @@ NEXT_PUBLIC_TINA_BRANCH=*** # These are analytics tags set in the repository secrets NEXT_PUBLIC_GOOGLE_GTM_ID=*** NEXT_PUBLIC_ZENDESK_CHAT_KEY=*** +NEXT_PUBLIC_APP_INSIGHT_CONNECTION_STRING=*** + +# Integration endpoints +NEWSLETTERS_ENDPOINT=*** +CREATE_LEAD_ENDPOINT=*** + +# displayed in the footer +NEXT_PUBLIC_GITHUB_RUN_DATE=2021-09-23T05:00:00.000Z # Current site URL SITE_URL=*** -# Client-side recaptcha key, to be removed -GOOGLE_RECAPTCHA_SITE_KEY=*** - -# New server-side recaptcha key - Previously it was GOOGLE_RECAPTCHA_KEY_v2 +# Recaptcha Keys GOOGLE_RECAPTCHA_KEY=*** +GOOGLE_RECAPTCHA_SITE_KEY=*** +RECAPTCHA_BYPASS_SECRET=foobar -# Microsoft OAuth Keys +# Microsoft OAuth Keys MICROSOFT_OAUTH_TENANT_ID=*** MICROSOFT_OAUTH_CLIENT_ID=*** MICROSOFT_OAUTH_CLIENT_SECRET=*** # SharePoint Site and List IDs +MICROSOFT_OAUTH_TENANT_ID=*** +MICROSOFT_OAUTH_CLIENT_ID=*** +MICROSOFT_OAUTH_CLIENT_SECRET=*** SHAREPOINT_SITE_ID=*** SHAREPOINT_EVENTS_LIST_ID=*** SHAREPOINT_EXTERNAL_PRESENTERS_LIST_ID=*** diff --git a/.github/workflows/weekly-ui-tests.yml b/.github/workflows/weekly-ui-tests.yml new file mode 100644 index 0000000000..10f1921534 --- /dev/null +++ b/.github/workflows/weekly-ui-tests.yml @@ -0,0 +1,79 @@ +name: Weekly UI Test - Enquiry Form + +on: + # workflow_dispatch: + + schedule: + # Monday at 9 PM UTC ~> 7 AM AEST - https://cron.help/#0_21_*_*_SUN + - cron: "0 21 * * SUN" + #pull_request: + # branches: + # - main + workflow_dispatch: + inputs: + HOST_URL: + type: string + description: "Host URL" + default: "https://ssw.com.au" + +env: + GH_TOKEN: ${{ github.token }} + +defaults: + run: + shell: pwsh + +permissions: + id-token: write + contents: read + +jobs: + test: + timeout-minutes: 10 + runs-on: ubuntu-latest + + steps: + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version-file: ".nvmrc" + + - name: Load .env file + uses: xom9ikk/dotenv@v2 + with: + path: ./.github + + - name: Azure CLI - Login + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: keyVault - Get Secret Key note + id: KeyVaultSecrets + run: | + # KV - Secret + $RECAPTCHA_BYPASS_SECRET = (az keyvault secret show --name SECRET-KEY-TO-BYPASS-RECAPTCHA --vault-name ${{ env.KEY_VAULT }} --query value -o tsv) + echo "::add-mask::$RECAPTCHA_BYPASS_SECRET" + echo "RECAPTCHA_BYPASS_SECRET=$RECAPTCHA_BYPASS_SECRET" >> $env:GITHUB_OUTPUT + Write-Host '✅ KV - Secret retrieved' + + - name: Install dependencies + run: yarn + + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + + - name: Run Playwright tests + run: yarn playwright test + env: + HOST_URL: ${{ inputs.HOST_URL }} + RECAPTCHA_BYPASS_SECRET: ${{ steps.KeyVaultSecrets.outputs.RECAPTCHA_BYPASS_SECRET }} + + - uses: actions/upload-artifact@v2 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 14 diff --git a/.gitignore b/.gitignore index d208944b23..963eed0d16 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,7 @@ yarn-error.log* # Partytown lib files /public/~partytown +# Playwright output +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 49fdc7e04b..851fadfafe 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,9 @@ { - "recommendations": [ - "bradlc.vscode-tailwindcss", - "esbenp.prettier-vscode", - "graphql.vscode-graphql", - "dbaeumer.vscode-eslint" - ] + "recommendations": [ + "bradlc.vscode-tailwindcss", + "esbenp.prettier-vscode", + "graphql.vscode-graphql", + "dbaeumer.vscode-eslint", + "ms-playwright.playwright" + ] } diff --git a/README.md b/README.md index eb4c7431fd..3097611c44 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,12 @@ syncyarnlock -s -k - : log out of Tina Cloud - : GraphQL playground to test queries and browse the API documentation +### UI Testing on local + +- 1 Make sure you have `CREATE_LEAD_ENDPOINT` environment variable in your `.env` - [Follow steps to setup](https://github.com/SSWConsulting/SSW.Website/wiki/Accessing-the-Third%E2%80%90Party-APIs-Locally) + +- 2 Run `yarn playwright test --ui` in your terminal and make sure your local instance is running in the background. + ## Pull Requests Each Pull Request will be deployed to its own staging environment, the URL to the environment is available in the PR thread. diff --git a/components/bookingButton/bookingButton.tsx b/components/bookingButton/bookingButton.tsx index 0c28da1b9e..a558ebe640 100644 --- a/components/bookingButton/bookingButton.tsx +++ b/components/bookingButton/bookingButton.tsx @@ -35,7 +35,7 @@ export const BookingButton = ({ data }) => { const showSuccessToast = () => { toast.success( -
+
Form submitted. We'll be in contact as soon as possible.
); diff --git a/components/successToast/successToast.tsx b/components/successToast/successToast.tsx index f1b1d58d2f..27ed1dd15e 100644 --- a/components/successToast/successToast.tsx +++ b/components/successToast/successToast.tsx @@ -1,5 +1,5 @@ -import "react-toastify/dist/ReactToastify.css"; import { ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; const SuccessToast = () => { return ( diff --git a/infra/appSerivce-create-slot.bicep b/infra/appSerivce-create-slot.bicep index 40708b63fa..5b94c7f473 100644 --- a/infra/appSerivce-create-slot.bicep +++ b/infra/appSerivce-create-slot.bicep @@ -64,6 +64,10 @@ var appSettings = [ name: 'SHAREPOINT_EXTERNAL_PRESENTERS_LIST_ID' value: '@Microsoft.KeyVault(SecretUri=https://${keyVaultName}.vault.azure.net/secrets/SHAREPOINT-EXTERNAL-PRESENTERS-LIST-ID)' } + { + name: 'RECAPTCHA_BYPASS_SECRET' + value: '@Microsoft.KeyVault(SecretUri=https://${keyVaultName}.vault.azure.net/secrets/SECRET-KEY-TO-BYPASS-RECAPTCHA)' + } ] diff --git a/infra/appService.bicep b/infra/appService.bicep index a9cac8465b..e6d66f04fb 100644 --- a/infra/appService.bicep +++ b/infra/appService.bicep @@ -109,6 +109,10 @@ var appSettings = [ name: 'SHAREPOINT_EXTERNAL_PRESENTERS_LIST_ID' value: '@Microsoft.KeyVault(SecretUri=https://${keyVaultName}.vault.azure.net/secrets/SHAREPOINT-EXTERNAL-PRESENTERS-LIST-ID)' } + { + name: 'RECAPTCHA_BYPASS_SECRET' + value: '@Microsoft.KeyVault(SecretUri=https://${keyVaultName}.vault.azure.net/secrets/SECRET-KEY-TO-BYPASS-RECAPTCHA)' + } ] resource appService 'Microsoft.Web/sites@2022-03-01' = { diff --git a/package.json b/package.json index 8ba3d8a7ec..4ed61adc16 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@next/eslint-plugin-next": "^13.4.19", + "@playwright/test": "^1.38.1", "@svgr/webpack": "^8.1.0", "@tinacms/cli": "^1.5.29", "@types/js-cookie": "^3.0.4", @@ -51,6 +52,7 @@ "axios": "^1.5.1", "classnames": "^2.3.2", "dayjs": "^1.11.10", + "dotenv": "^16.3.1", "eslint-config-next": "13.5.3", "formik": "^2.4.5", "next": "13.5.2", diff --git a/pages/api/create-lead.ts b/pages/api/create-lead.ts index f7d4220480..6f4b873589 100644 --- a/pages/api/create-lead.ts +++ b/pages/api/create-lead.ts @@ -12,16 +12,36 @@ import { invokePowerAutomateFlow } from "../../services/server/power-automate-fl import { CustomError } from "../../services/server/customError"; +const RECAPATCHA_VALIDATION_SUCCESS_RESULT = { + data: { + success: true, + }, + status: 200, +}; + export default async function handler( req: NextApiRequest, res: NextApiResponse ) { try { if (req.method === "POST") { + // this is the code to validate with the recaptcha service const { Recaptcha } = req.body; + + const Note = req.body.Note; + // Note: bypassing recaptcha is intended for weekly testing the lead capture form only + const key_matched = Note.includes(process.env.RECAPTCHA_BYPASS_SECRET); + + if (key_matched) { + req.body.Note = Note.replace(process.env.RECAPTCHA_BYPASS_SECRET, ""); + } + // Documentation - Create Lead - https://sswcom.sharepoint.com/:w:/r/sites/SSWDevelopers/_layouts/15/Doc.aspx?sourcedoc=%7BE8A18D9B-DE74-47EC-B836-01A5AD193DCC%7D&file=Create-lead-Flow.docx&action=default&mobileredirect=true - if (Recaptcha) { - const recaptchaValidation = await validateRecaptcha(Recaptcha); + if (Recaptcha || key_matched) { + // Recaptcha value provided by google Recaptcha API + const recaptchaValidation = key_matched + ? RECAPATCHA_VALIDATION_SUCCESS_RESULT + : await validateRecaptcha(Recaptcha); // const recaptchaValidation = { data: { success: true } }; // uncomment this to bypass recaptcha for testing purpose if (recaptchaValidation && recaptchaValidation.data.success) { const createLeadFlow = await invokePowerAutomateFlow( diff --git a/pages/consulting/[filename].tsx b/pages/consulting/[filename].tsx index 37cbfc25c5..5c7c59bc2d 100644 --- a/pages/consulting/[filename].tsx +++ b/pages/consulting/[filename].tsx @@ -61,7 +61,9 @@ export default function ConsultingPage( return ( diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000..86e1fbaa29 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,79 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ + +// eslint-disable-next-line @typescript-eslint/no-var-requires +require("dotenv").config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./ui-tests", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.HOST_URL || "http://localhost:3000", + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + // { + // name: "firefox", + // use: { ...devices["Desktop Firefox"] }, + // }, + + // { + // name: "webkit", + // use: { ...devices["Desktop Safari"] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/ui-tests/components/bookingForm.spec.ts b/ui-tests/components/bookingForm.spec.ts new file mode 100644 index 0000000000..c8a65e39b7 --- /dev/null +++ b/ui-tests/components/bookingForm.spec.ts @@ -0,0 +1,41 @@ +import { expect, test } from "@playwright/test"; + +test("Can submit the booking form", async ({ page }) => { + await page.goto("/consulting/angular", { waitUntil: "networkidle" }); + + await page + .getByRole("button") + .getByText("Book a FREE Initial Meeting") + .first() + .click(); + + await page.waitForSelector(".react-responsive-modal-root form"); + + // note: preference would be to reference by label, but the names aren't consistent with the field names + // note: getByPlaceholder is doing a partial match (the asterisks are hardcoded into the placeholder text) 🤮 + await page.getByPlaceholder("your full name").fill("🧪 Test"); + await page.getByPlaceholder("your email").fill("amankumar@ssw.com.au"); + await page.getByPlaceholder("your phone").fill("0000000000"); + await page.locator("select[name='location']").selectOption("Australia"); // placeholder looks like location, but its actually an unselectable option + await page.locator("select[name='states']").selectOption("100000001"); + await page.getByPlaceholder("your company").fill("Test"); + await page.locator("select[name='referralSource']").selectOption("14"); + await page + .getByPlaceholder("how can we help you") + .fill( + `

Hi Account Managers,

This is a weekly test email to verify that the create lead flow is working. ${ + process.env.RECAPTCHA_BYPASS_SECRET ?? "No Key found" + }` + ); + + await page.locator("button[type='submit']").first().click(); + + const successToastId = "#success-toaster"; + await page.waitForSelector(successToastId); + const toastElement = await page.locator(successToastId).first(); + const toastText = await toastElement.textContent(); + + await expect(toastText).toBe( + "Form submitted. We'll be in contact as soon as possible." + ); +}); diff --git a/yarn.lock b/yarn.lock index d49e64e86b..bc3af8f68d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3227,6 +3227,17 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:^1.38.1": + version: 1.38.1 + resolution: "@playwright/test@npm:1.38.1" + dependencies: + playwright: 1.38.1 + bin: + playwright: cli.js + checksum: c5ec0b23261fe1ef163b6234f69263bc10e7e5a3fb676c7773ffc70b87459a7ab225f57c03b9de649475771638a04c2e00d9b2739304a4dcf5d3edf20a7a4a82 + languageName: node + linkType: hard + "@polka/url@npm:^1.0.0-next.20": version: 1.0.0-next.21 resolution: "@polka/url@npm:1.0.0-next.21" @@ -4575,6 +4586,7 @@ __metadata: "@microsoft/applicationinsights-web": 2.8.14 "@next/bundle-analyzer": ^13.4.19 "@next/eslint-plugin-next": ^13.4.19 + "@playwright/test": ^1.38.1 "@svgr/webpack": ^8.1.0 "@tailwindcss/typography": ^0.5.10 "@tinacms/cli": ^1.5.29 @@ -4590,6 +4602,7 @@ __metadata: axios: ^1.5.1 classnames: ^2.3.2 dayjs: ^1.11.10 + dotenv: ^16.3.1 eslint: ^8.49.0 eslint-config-next: 13.5.3 eslint-config-prettier: ^8.8.0 @@ -8066,6 +8079,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.3.1": + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: 15d75e7279018f4bafd0ee9706593dd14455ddb71b3bcba9c52574460b7ccaf67d5cf8b2c08a5af1a9da6db36c956a04a1192b101ee102a3e0cf8817bbcf3dfd + languageName: node + linkType: hard + "downshift@npm:^6.1.7": version: 6.1.12 resolution: "downshift@npm:6.1.12" @@ -9382,7 +9402,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -9392,7 +9412,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@~2.3.2#~builtin": +"fsevents@patch:fsevents@2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -13095,6 +13115,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.38.1": + version: 1.38.1 + resolution: "playwright-core@npm:1.38.1" + bin: + playwright-core: cli.js + checksum: 66e83fe040f309b13ad94ba39dea40ac207bfcbbc22de13141af88dbdedd64e1c4e3ce1d0cb070d4efd8050d7e579953ec3681dd8a0acf2c1cc738d9c50e545e + languageName: node + linkType: hard + +"playwright@npm:1.38.1": + version: 1.38.1 + resolution: "playwright@npm:1.38.1" + dependencies: + fsevents: 2.3.2 + playwright-core: 1.38.1 + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 4e01d4ee52d9ccf75a80d8492829106802590721d56bff7c5957ff1f21eb3c328ee5bc3c1784a59c4b515df1b98d08ef92e4a35a807f454cd00dc481d30fadc2 + languageName: node + linkType: hard + "popmotion@npm:11.0.3": version: 11.0.3 resolution: "popmotion@npm:11.0.3"