diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml
new file mode 100644
index 0000000..3eb1314
--- /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 ci
+ - name: Install Playwright Browsers
+ run: npx playwright install --with-deps
+ - name: Run Playwright tests
+ run: npx 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 5ef6a52..546c174 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,11 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+# Playwright
+node_modules/
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
+/playwright/.auth/
diff --git a/app/page.tsx b/app/page.tsx
index 2b8b225..a8ae903 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,16 +1,17 @@
-import Link from 'next/link';
import Grid from '../components/grid';
import Player from '../components/player';
export default function Home() {
return (
-
Click here to see testing
-
+
diff --git a/package-lock.json b/package-lock.json
index 320b020..6467b90 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,6 +19,7 @@
},
"devDependencies": {
"@eslint/eslintrc": "^3",
+ "@playwright/test": "^1.58.2",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
@@ -911,6 +912,21 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
+ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
+ "dev": true,
+ "dependencies": {
+ "playwright": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1267,7 +1283,7 @@
"version": "19.1.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz",
"integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -1276,7 +1292,7 @@
"version": "19.1.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz",
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
- "devOptional": true,
+ "dev": true,
"peerDependencies": {
"@types/react": "^19.0.0"
}
@@ -2260,7 +2276,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true
+ "dev": true
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -3190,6 +3206,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "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/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -4721,6 +4751,36 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
+ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
+ "dev": true,
+ "dependencies": {
+ "playwright-core": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
+ "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
+ "dev": true,
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
diff --git a/package.json b/package.json
index 2606f45..3544361 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
},
"devDependencies": {
"@eslint/eslintrc": "^3",
+ "@playwright/test": "^1.58.2",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 0000000..8829e37
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,68 @@
+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('')`. */
+ // baseURL: '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'] },
+ },
+ /* 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://localhost:3000',
+ // reuseExistingServer: !process.env.CI,
+ // },
+});
diff --git a/tests/example.spec.ts b/tests/example.spec.ts
new file mode 100644
index 0000000..4286bb4
--- /dev/null
+++ b/tests/example.spec.ts
@@ -0,0 +1,42 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Equivalence partitioning', () => {
+ test('has title', async ({ page }) => {
+ await page.goto('https://playwright.dev/');
+
+ // Expect a title "to contain" a substring.
+ await expect(page).toHaveTitle(/Playwright/);
+ });
+
+ test(
+ 'Verify that valid moves (empty cells) are accepted and invalid moves (occupied cells) are rejected using equivalence partitions.',
+ {
+ annotation: [{ type: 'ticket-id', description: 'TC00-1' }],
+ },
+ async ({ page }) => {
+ await page.goto('https://playwright.dev/');
+
+ // Click the get started link.
+ await page.getByRole('link', { name: 'Get started' }).click();
+
+ const installationHeading = page.getByRole('heading', {
+ name: 'Installation',
+ });
+
+ // Expects page to have a heading with the name of Installation.
+ await expect(installationHeading).toBeVisible();
+ },
+ );
+
+ test('visits torun website', async ({ page }) => {
+ await page.goto('https://www.google.com');
+ await page.getByRole('button', { name: 'Alle akzeptieren' }).click();
+ await page.getByRole('combobox', { name: 'Suche' }).fill('Torun Wikström');
+ await page
+ .getByRole('button', { name: 'Google Suche', exact: true })
+ .first()
+ .click();
+
+ await page.getByText('Torun Wikström');
+ });
+});