From 8a8673dea20e01ff96c851cdbb6d169c3891ef09 Mon Sep 17 00:00:00 2001 From: minkyngkm Date: Thu, 7 Dec 2023 17:27:03 +0900 Subject: [PATCH] Set up playwright --- .env | 2 +- .github/workflows/cypress-pr.yaml | 42 ---------------- .github/workflows/playwright.yml | 65 +++++++++++++++++++++++++ .gitignore | 3 ++ package.json | 6 +++ playwright.config.ts | 42 ++++++++++++++++ tests/playwright/helplers/commands.ts | 42 ++++++++++++++++ tests/playwright/helplers/mockData.ts | 59 ++++++++++++++++++++++ tests/playwright/helplers/utils.ts | 4 ++ tests/playwright/tests/checkout.spec.ts | 44 +++++++++++++++++ yarn.lock | 40 ++++++++++++++- 11 files changed, 305 insertions(+), 44 deletions(-) delete mode 100644 .github/workflows/cypress-pr.yaml create mode 100644 .github/workflows/playwright.yml create mode 100644 playwright.config.ts create mode 100644 tests/playwright/helplers/commands.ts create mode 100644 tests/playwright/helplers/mockData.ts create mode 100644 tests/playwright/helplers/utils.ts create mode 100644 tests/playwright/tests/checkout.spec.ts diff --git a/.env b/.env index 0d6a1e1e5d0..2a9e8b3fc65 100644 --- a/.env +++ b/.env @@ -16,4 +16,4 @@ CONFIDENTIALITY_AGREEMENT_WEBHOOK_USERNAME="local" CONFIDENTIALITY_AGREEMENT_WEBHOOK_PASSWORD="password" # This is a test api key https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do -CAPTCHA_TESTING_API_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI +CAPTCHA_TESTING_API_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI \ No newline at end of file diff --git a/.github/workflows/cypress-pr.yaml b/.github/workflows/cypress-pr.yaml deleted file mode 100644 index 6c6778ebd5e..00000000000 --- a/.github/workflows/cypress-pr.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: Cypress checks / PR -env: - SECRET_KEY: insecure_test_key - PORT: 8001 - CONTRACTS_API_URL: https://contracts.staging.canonical.com - STRIPE_PUBLISHABLE_KEY: pk_test_yndN9H0GcJffPe0W58Nm64cM00riYG4N46 - CAPTCHA_TESTING_API_KEY: 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI - MARKETO_API_CLIENT: ${{secrets.MARKETO_API_CLIENT}} - MARKETO_API_SECRET: ${{secrets.MARKETO_API_SECRET}} - -on: pull_request - -jobs: - run-cypress: - if: github.repository == 'canonical/ubuntu.com' - runs-on: ubuntu-latest - - steps: - - name: Checkout main - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.10" - - - name: Install python dependencies - run: pip3 install -r requirements.txt - - - name: Cypress run - uses: cypress-io/github-action@v2 - with: - # This is a dummy account that contains no sensitive information and is only used for testing - # This is to workaround the fact that GitHub secrets cannot be used in pull requests from forks - env: UBUNTU_USERNAME=peter.makowski+dummy-cypress-account@canonical.com,UBUNTU_PASSWORD=9BKfSNo2&2Zs - build: yarn run build - start: yarn run serve - wait-on: "http://0.0.0.0:8001/_status/check" - wait-on-timeout: 300 - browser: chrome - config-file: tests/cypress/cypress.json - config: baseUrl=http://0.0.0.0:8001 diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 00000000000..8685db2283b --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,65 @@ +name: Playwright Tests +env: + SECRET_KEY: insecure_test_key + PORT: 8001 + CONTRACTS_API_URL: https://contracts.staging.canonical.com + STRIPE_PUBLISHABLE_KEY: pk_test_yndN9H0GcJffPe0W58Nm64cM00riYG4N46 + CAPTCHA_TESTING_API_KEY: 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI + MARKETO_API_CLIENT: ${{secrets.MARKETO_API_CLIENT}} + MARKETO_API_SECRET: ${{secrets.MARKETO_API_SECRET}} + PLAYWRIGHT_USER_ID: ${{secrets.PLAYWRIGHT_USER_ID}} + PLAYWRIGHT_USER_PASSWORD : ${{secrets.PLAYWRIGHT_USER_PASSWORD}} +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + if: github.repository == 'canonical/ubuntu.com' + runs-on: ubuntu-latest + steps: + - name: Echo GitHub Secret + run: echo ${{ secrets.PLAYWRIGHT_USER_ID }} ${{ secrets.PLAYWRIGHT_USER_PASSWORD }} + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install python dependencies + run: pip3 install -r requirements.txt + - name: Install dependencies + run: npm install -g yarn && yarn + - name: Install dotrun + uses: canonical/install-dotrun@main + + - name: Install dependencies + run: /snap/bin/dotrun install + + - name: Build assets + run: /snap/bin/dotrun build + + - name: Run dotrun + run: | + /snap/bin/dotrun & + curl --head --fail --retry-delay 1 --retry 30 --retry-connrefused http://localhost:8001 + + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run Playwright tests + run: yarn playwright test + env: + PLAYWRIGHT_USER_ID: ${{secrets.PLAYWRIGHT_USER_ID}} + PLAYWRIGHT_USER_PASSWORD : ${{secrets.PLAYWRIGHT_USER_PASSWORD}} + - uses: actions/upload-artifact@v3 + if: always() + with: + build: yarn run build + start: yarn run serve + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index 580c3c286aa..cf3dc3a14bd 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,6 @@ webapp/snapcraft_api/ .login static/js/build/ tests/cassettes/TestRoutes.test_ceph_docs.yaml +/test-results/ +/playwright-report/ +/playwright/ \ No newline at end of file diff --git a/package.json b/package.json index 2b179b633a9..05f5a2b1edd 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@babel/preset-env": "7.16.7", "@babel/preset-react": "7.14.5", "@babel/preset-typescript": "7.16.7", + "@playwright/test": "^1.40.0", "@testing-library/cypress": "8.0.0", "@testing-library/dom": "8.13.0", "@testing-library/jest-dom": "5.14.1", @@ -52,6 +53,7 @@ "@types/enzyme": "3.10.11", "@types/jest": "26.0.24", "@types/lodash": "4.14.175", + "@types/node": "^20.9.3", "@types/react-google-recaptcha": "2.1.2", "@typescript-eslint/eslint-plugin": "4.33.0", "@typescript-eslint/parser": "4.33.0", @@ -97,6 +99,7 @@ "autoprefixer": "10.4.1", "date-fns": "2.28.0", "date-fns-tz": "1.3.4", + "dotenv": "^16.3.1", "esbuild": "0.14.10", "flickity": "^3.0.0", "formik": "2.2.9", @@ -127,6 +130,9 @@ "node_modules", "src" ], + "testPathIgnorePatterns": [ + "/tests/playwright/" + ], "setupFilesAfterEnv": [ "/tests/setupTests.ts" ], diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000000..60e728ef2a1 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,42 @@ +import { defineConfig, devices } from '@playwright/test'; +import * as dotenv from 'dotenv'; +import * as path from "path"; + +dotenv.config({ + path: path.join(__dirname, ".env.local") +}); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: path.join(__dirname, "tests/playwright/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. */ + timeout: 30000, + use: { + baseURL: "http://0.0.0.0:8001", + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + ignoreHTTPSErrors: true, + headless: true, + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'checkout', + testMatch: "*.spec.ts", + use: { ...devices['Desktop Chrome']}, + }, + ], +}); diff --git a/tests/playwright/helplers/commands.ts b/tests/playwright/helplers/commands.ts new file mode 100644 index 00000000000..5b37f708008 --- /dev/null +++ b/tests/playwright/helplers/commands.ts @@ -0,0 +1,42 @@ +import { Page } from "@playwright/test"; + +export const login = async (page: Page) => { + // TODO: mocking Login (To intercept "https://login.ubuntu.com/*/+login", proper mock responses are needed) + + await acceptCookiePolicy(page); + await page.fill('input[name="email"]', process.env.PLAYWRIGHT_USER_ID as string) ; + await page.fill('input[name="password"]', process.env.PLAYWRIGHT_USER_PASSWORD as string); + await page.click('button[type="submit"]') // Click "Login" + await page.click('button[type="submit"]') // Click "Yes, log me in" +} + +export const selectProducts = async ( + page: Page, + productUser = "organisation", + quantity = 2, + machineType = "physical", + version = "20.04", + support = "none" +) => { + await page.locator(`[value='${productUser}']`).check(); + await page.locator("#quantity-input").fill(`${quantity}`); + await page.locator(`[value='${machineType}']`).check(); + await page.getByRole('tab', { name: `${version} LTS`}).click(); + await page.getByRole('radio', { name: 'Ubuntu Pro (Infra-only)', exact: true }).check(); + await page.$(`#${support}-label`) +}; + +export const acceptCookiePolicy = async ( + page: Page, +) => { + await page.locator('#cookie-policy-button-accept').click(); +}; + +export const acceptTerms = async (page: Page) => { + await page.getByText(/I agree to the Ubuntu Pro service terms/).click(); + await page.getByText(/I agree to the Ubuntu Pro description/).click(); +} + +export const clickRecaptcha = async (page: Page) => { + await page.frameLocator('[title="reCAPTCHA"]').getByRole('checkbox', { name: 'I\'m not a robot' }).click({force: true}); +} \ No newline at end of file diff --git a/tests/playwright/helplers/mockData.ts b/tests/playwright/helplers/mockData.ts new file mode 100644 index 00000000000..29eb5fec940 --- /dev/null +++ b/tests/playwright/helplers/mockData.ts @@ -0,0 +1,59 @@ +// post /pro/purchase/preview${window.location.search} +export const previewResponse = { + "currency": "usd", + "end_of_cycle": "", + "id": "", + "items": null, + "payment_status": null, + "reason": "subscription_create", + "start_of_cycle": "", + "status": "draft", + "tax_amount": null, + "total": 45000, + "url": null +} + +// post /account/customer-info/${accountId}${queryString} +export const customerInfoResponse = { + "accountInfo": { + "createdAt": "2023-06-21T10:19:18Z", + "externalAccountIDs": [ + { + "IDs": [ + "cus_OAc0ko9kKLLpn2" + ], + "origin": "Stripe" + }, + { + "IDs": [ + "0013M00001QN0K4QAL" + ], + "origin": "Salesforce" + } + ], + "id": "aACd-Dgydz9UmVpft445tErM1NIVHbVhX-G7bbxzAWgQ", + "lastModifiedAt": "2023-08-08T11:04:08Z", + "name": "Canonical", + "type": "paid" + }, + "customerInfo": { + "address": { + "city": "test", + "country": "JP", + "line1": "test 2", + "line2": "", + "postal_code": "test", + "state": "" + }, + "defaultPaymentMethod": { + "brand": "visa", + "country": "US", + "expMonth": 4, + "expYear": 2024, + "id": "pm_1OJqfSCzjFajHovdEN8RvPf8", + "last4": "4242" + }, + "email": "min.kim+nopayment15@canonical.com", + "name": "MIn Kim" + } +} diff --git a/tests/playwright/helplers/utils.ts b/tests/playwright/helplers/utils.ts new file mode 100644 index 00000000000..013109eedb2 --- /dev/null +++ b/tests/playwright/helplers/utils.ts @@ -0,0 +1,4 @@ +export const ENDPOINTS = { + customerInfo: "/account/customer-info*", + preview: "/pro/purchase/preview*" + }; \ No newline at end of file diff --git a/tests/playwright/tests/checkout.spec.ts b/tests/playwright/tests/checkout.spec.ts new file mode 100644 index 00000000000..5014b7454a3 --- /dev/null +++ b/tests/playwright/tests/checkout.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from "@playwright/test"; +import { selectProducts, acceptCookiePolicy, login } from "../helplers/commands"; +import { customerInfoResponse, previewResponse } from "../helplers/mockData"; +import { ENDPOINTS } from "../helplers/utils"; + +test.describe("Checkout - Region and taxes", () => { + test("It should show correct non-VAT price", async ({page}) => { + await page.goto("/pro/subscribe") + await acceptCookiePolicy(page) + await selectProducts(page); + await page.getByRole("button", { name: "Buy now" }).click(); + + await login(page); + + await page.route(ENDPOINTS.customerInfo, async (route) => { + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({...customerInfoResponse, "customerInfo": {...customerInfoResponse.customerInfo, "address": {...customerInfoResponse.customerInfo.address, "country": "AF" }}}) + }); + }); + + await page.route(ENDPOINTS.preview, async (route) => { + route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(previewResponse) + }); + }); + + await page.locator( + ":nth-child(1) > .p-stepped-list__content > .row > .u-align--right > .p-action-button" + ).click(); // Click "Edit" button + await page.getByLabel("Country/Region:").selectOption({ label: 'Afghanistan' }) + await page.locator(".u-align--right > :nth-child(2)").click(); // Click "Save" button + + const country = await page.$('[data-testid="country"]') + const countryText = await country?.innerText(); + + expect(countryText).toBe("Afghanistan") + expect(await page.$('[data-testid="total"]')).toBeNull(); + expect(await page.$('[data-testid="tax"]')).toBeNull(); + }) +}) diff --git a/yarn.lock b/yarn.lock index a89330c0acb..0dffc286c22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1412,6 +1412,13 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@playwright/test@^1.40.0": + version "1.40.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.40.0.tgz#d06c506977dd7863aa16e07f2136351ecc1be6ed" + integrity sha512-PdW+kn4eV99iP5gxWNSDQCbhMaDVej+RXL5xr6t04nbKLCBwYtA046t7ofoczHOm8u6c+45hpDKQVZqtqwkeQg== + dependencies: + playwright "1.40.0" + "@reduxjs/toolkit@1.7.1": version "1.7.1" resolved "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.7.1.tgz" @@ -1746,6 +1753,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-14.17.14.tgz" integrity sha512-rsAj2u8Xkqfc332iXV12SqIsjVi07H479bOP4q94NAcjzmAvapumEhuVIt53koEf7JFrpjgNKjBga5Pnn/GL8A== +"@types/node@^20.9.3": + version "20.9.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.3.tgz#e089e1634436f676ff299596c9531bd2b59fffc6" + integrity sha512-nk5wXLAXGBKfrhLB0cyHGbSqopS+nz0BUgZkUQqSHSSgdee0kssp1IAqlQOu333bW+gMNs2QREx7iynm19Abxw== + dependencies: + undici-types "~5.26.4" + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz" @@ -3394,6 +3408,11 @@ domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0: domelementtype "^2.2.0" domhandler "^4.2.0" +dotenv@^16.3.1: + version "16.3.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" + integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" @@ -4249,7 +4268,7 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.1.2, fsevents@~2.3.2: +fsevents@2.3.2, fsevents@^2.1.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -6740,6 +6759,20 @@ pkg-dir@4.2.0, pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +playwright-core@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.40.0.tgz#82f61e5504cb3097803b6f8bbd98190dd34bdf14" + integrity sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q== + +playwright@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.40.0.tgz#2a1824b9fe5c4fe52ed53db9ea68003543a99df0" + integrity sha512-gyHAgQjiDf1m34Xpwzaqb76KgfzYrhK7iih+2IzcOCoZWr/8ZqmdBw+t0RU85ZmfJMgtgAiNtBQ/KS2325INXw== + dependencies: + playwright-core "1.40.0" + optionalDependencies: + fsevents "2.3.2" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz" @@ -8430,6 +8463,11 @@ unbzip2-stream@1.3.3: buffer "^5.2.1" through "^2.3.8" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz"