diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..61d55b0 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,36 @@ +name: Playwright Tests +on: + pull_request: + branches: + - main + +jobs: + test_setup: + name: Test setup + runs-on: ubuntu-latest + outputs: + preview_url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }} + steps: + - name: Wait for Vercel preview deployment to be ready + uses: patrickedqvist/wait-for-vercel-preview@main + id: waitForVercelPreviewDeployment + with: + token: ${{ secrets.GITHUB_TOKEN }} + max_timeout: 300 + test_e2e: + needs: test_setup + name: Playwright tests + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - name: Prepare testing env + uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "16" + - run: npm ci + - run: npx playwright install --with-deps + - name: Run tests + run: npm run test:e2e + env: + PLAYWRIGHT_TEST_BASE_URL: ${{ needs.test_setup.outputs.preview_url }} diff --git a/.gitignore b/.gitignore index ad3aae1..74216f6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ build/ # macOS .DS_Store + +# playwright +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index e092ed6..b55d2bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "webnative": "^0.35.1" }, "devDependencies": { + "@playwright/test": "^1.29.2", "@sveltejs/adapter-static": "1.0.0-next.43", "@sveltejs/kit": "1.0.0-next.489", "@tailwindcss/typography": "^0.5.2", @@ -430,6 +431,22 @@ "node": ">= 8" } }, + "node_modules/@playwright/test": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.2.tgz", + "integrity": "sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.29.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -4170,6 +4187,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/playwright-core": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.2.tgz", + "integrity": "sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/plur": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", @@ -6094,6 +6123,16 @@ "fastq": "^1.6.0" } }, + "@playwright/test": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.2.tgz", + "integrity": "sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==", + "dev": true, + "requires": { + "@types/node": "*", + "playwright-core": "1.29.2" + } + }, "@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -8710,6 +8749,12 @@ "load-json-file": "^7.0.0" } }, + "playwright-core": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.2.tgz", + "integrity": "sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA==", + "dev": true + }, "plur": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", diff --git a/package.json b/package.json index 2d43fc8..680b204 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,15 @@ "build": "vite build", "preview": "vite preview", "test": "ava src/**/*.test.ts", + "test:e2e": "playwright test", + "report:e2e": "playwright show-report", "check": "svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", "lint": "eslint './src/**/*.{js,ts,svelte}'", "format": "prettier --write --plugin-search-dir=. ." }, "devDependencies": { + "@playwright/test": "^1.29.2", "@sveltejs/adapter-static": "1.0.0-next.43", "@sveltejs/kit": "1.0.0-next.489", "@tailwindcss/typography": "^0.5.2", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..e508110 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,93 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000 + }, + /* 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: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://127.0.0.1:5173', + + /* 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'], + }, + }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + port: 5173 + } +} + +export default config; diff --git a/tests/homepage.spec.ts b/tests/homepage.spec.ts new file mode 100644 index 0000000..ed83d80 --- /dev/null +++ b/tests/homepage.spec.ts @@ -0,0 +1,11 @@ +import { test, expect } from '@playwright/test' + +test('find and click register link', async ({ page }) => { + await page.goto('/') + + // Click the register link. + await page.getByRole('link', { name: 'Connect this device' }).click() + + // Expects the URL to contain register. + await expect(page).toHaveURL(/.*register/) +})