From 9552f8b6f5d5700d0d840439dc2b8a0239716027 Mon Sep 17 00:00:00 2001 From: Subho Date: Fri, 26 Jul 2024 10:39:22 +0530 Subject: [PATCH] Add playwright test - the playwright.config.ts is generated by `npm init playwright@latest` - the test job is inspired by generated .github/workflows/playwright.yml file --- .github/workflows/format-lint-test.yml | 13 +++ .gitignore | 4 + package-lock.json | 60 ++++++++++++++ package.json | 3 +- playwright.config.ts | 78 ++++++++++++++++++ tests/example.spec.ts | 44 ++++++++++ ...nsion-in-New-Tab-page-1-chromium-linux.png | Bin 0 -> 22133 bytes 7 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 playwright.config.ts create mode 100644 tests/example.spec.ts create mode 100644 tests/example.spec.ts-snapshots/Extension-in-New-Tab-page-1-chromium-linux.png diff --git a/.github/workflows/format-lint-test.yml b/.github/workflows/format-lint-test.yml index 26a5f8e..3f7b922 100644 --- a/.github/workflows/format-lint-test.yml +++ b/.github/workflows/format-lint-test.yml @@ -28,6 +28,7 @@ jobs: run: npm run lint:check test: + timeout-minutes: 10 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -36,5 +37,17 @@ jobs: node-version: '20' - name: Install dependencies run: npm ci + - name: Build extension + run: npm run build + - name: Install Playwright Browsers + run: npx playwright install --with-deps chromium - name: Run tests run: npm run test + + # Upload test results on failure + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index f06235c..1e5f630 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ node_modules dist +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index cb3197c..4e459fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ }, "devDependencies": { "@biomejs/biome": "1.8.3", + "@playwright/test": "^1.45.3", "@types/chrome": "^0.0.269", "@types/node": "^20.14.12", "@types/react": "^18.3.3", @@ -733,6 +734,21 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz", + "integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==", + "dev": true, + "dependencies": { + "playwright": "1.45.3" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", @@ -2579,6 +2595,50 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.3.tgz", + "integrity": "sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==", + "dev": true, + "dependencies": { + "playwright-core": "1.45.3" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.3.tgz", + "integrity": "sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.40", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz", diff --git a/package.json b/package.json index 355f498..0159ce6 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,11 @@ "lint:check": "biome lint .", "format:check": "biome format .", "format:fix": "biome format --write .", - "test": "echo \"For now, we don't have any tests.\"" + "test": "PW_TEST_HTML_REPORT_OPEN='never' npx playwright test" }, "devDependencies": { "@biomejs/biome": "1.8.3", + "@playwright/test": "^1.45.3", "@types/chrome": "^0.0.269", "@types/node": "^20.14.12", "@types/react": "^18.3.3", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..e4d6c27 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,78 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./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: 'http://127.0.0.1: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/tests/example.spec.ts b/tests/example.spec.ts new file mode 100644 index 0000000..9a61148 --- /dev/null +++ b/tests/example.spec.ts @@ -0,0 +1,44 @@ +import { test as base, type BrowserContext } from "@playwright/test"; +import { chromium } from "playwright"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// @ts-ignore: stop typescript from bitching about import.meta +const __dirname = path.dirname(import.meta.dirname); + +const test = base.extend<{ context: BrowserContext; extensionId: string }>({ + // biome-ignore lint: not use what this object destructuring is for + context: async ({}, use) => { + const pathToExtension = path.join(__dirname, "../dist"); + const context = await chromium.launchPersistentContext("", { + headless: false, + args: [ + "--headless=new", + `--disable-extensions-except=${pathToExtension}`, + `--load-extension=${pathToExtension}`, + ], + }); + await use(context); + await context.close(); + }, + extensionId: async ({ context }, use) => { + let [background] = context.serviceWorkers(); + if (!background) { + background = await context.waitForEvent("backgroundpage"); + } + const exntesionId = background.url().split("/")[2]; + await use(exntesionId); + }, +}); + +const expect = test.expect; + +test("Extension in New Tab page", async ({ page }) => { + await page.goto("https://playwright.dev/"); + await page.waitForTimeout(3000); + await page.goto("chrome://newtab"); + await expect(page).toHaveTitle("New Tab"); + await expect(page).toHaveScreenshot({ + mask: [page.locator("section > div:last-child > div:first-child svg")], + }); +}); diff --git a/tests/example.spec.ts-snapshots/Extension-in-New-Tab-page-1-chromium-linux.png b/tests/example.spec.ts-snapshots/Extension-in-New-Tab-page-1-chromium-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..3c09997bc049e0a4f993aa98367860ee02e80cb6 GIT binary patch literal 22133 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 literal 0 HcmV?d00001