diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..47a9eca --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm install -g pnpm && pnpm install + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run Playwright tests + run: pnpm exec playwright test + # - uses: actions/upload-artifact@v4 + # if: ${{ !cancelled() }} + # with: + # name: playwright-report + # path: playwright-report/ + # retention-days: 30 diff --git a/.gitignore b/.gitignore index 072a415..0bab531 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ coverage components.d.ts .vercel +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package.json b/package.json index 9f7ec48..96d8e08 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,10 @@ "format": "prettier --write src/", "prepare": "husky", "dev+preview": "bunx dev-and-preview@1.0.0", - "taze": "pnpx taze" + "taze": "pnpx taze", + "playwright": "playwright test", + "playwright:ui": "playwright test --ui", + "playwright:chromium": "playwright test --project=chromium" }, "lint-staged": { "src/**/*.{js,ts,vue}": [ @@ -56,6 +59,7 @@ "devDependencies": { "@iconify-json/carbon": "^1.2.1", "@iconify/utils": "^2.1.33", + "@playwright/test": "^1.47.2", "@rushstack/eslint-patch": "^1.10.4", "@tsconfig/node20": "^20.1.4", "@types/jsdom": "^21.1.7", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..29296f0 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,80 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// 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('/')`. */ + // https://vercel.com/guides/how-can-i-run-end-to-end-tests-after-my-vercel-preview-deployment + baseURL: process.env.BASE_URL || 'https://vue-ts-example.oo1.dev', + + /* 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/pnpm-lock.yaml b/pnpm-lock.yaml index 39833d4..3d878ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,6 +87,9 @@ importers: '@iconify/utils': specifier: ^2.1.33 version: 2.1.33 + '@playwright/test': + specifier: ^1.47.2 + version: 1.47.2 '@rushstack/eslint-patch': specifier: ^1.10.4 version: 1.10.4 @@ -801,6 +804,11 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@playwright/test@1.47.2': + resolution: {integrity: sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==} + engines: {node: '>=18'} + hasBin: true + '@polka/url@1.0.0-next.28': resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} @@ -2433,6 +2441,11 @@ packages: os: [darwin] deprecated: '"Please update to latest v2.3 or v2.2"' + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -3363,6 +3376,16 @@ packages: pkg-types@1.2.0: resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} + playwright-core@1.47.2: + resolution: {integrity: sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.47.2: + resolution: {integrity: sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==} + engines: {node: '>=18'} + hasBin: true + postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} @@ -5002,6 +5025,10 @@ snapshots: '@pkgr/core@0.1.1': {} + '@playwright/test@1.47.2': + dependencies: + playwright: 1.47.2 + '@polka/url@1.0.0-next.28': {} '@rollup/pluginutils@4.2.1': @@ -6996,6 +7023,9 @@ snapshots: fsevents@2.1.3: optional: true + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -7887,6 +7917,14 @@ snapshots: mlly: 1.7.1 pathe: 1.1.2 + playwright-core@1.47.2: {} + + playwright@1.47.2: + dependencies: + playwright-core: 1.47.2 + optionalDependencies: + fsevents: 2.3.2 + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 diff --git a/tests/example.spec.ts b/tests/example.spec.ts new file mode 100644 index 0000000..54a906a --- /dev/null +++ b/tests/example.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Playwright/); +}); + +test('get started link', async ({ page }) => { + await page.goto('https://playwright.dev/'); + + // Click the get started link. + await page.getByRole('link', { name: 'Get started' }).click(); + + // Expects page to have a heading with the name of Installation. + await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible(); +});