diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..137bdc1 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,33 @@ +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@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx playwright install --with-deps + + - name: Build App + run: npm run build + + - name: Run Playwright tests + run: npx playwright test + + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.gitignore b/.gitignore index e1943a5..4c1cb9b 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,10 @@ next-env.d.ts # Vscode .vs/* /test-results/ + +#Playwright + /playwright-report/ /blob-report/ /playwright/.cache/ +/playwright/.auth/ diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..1cd3294 --- /dev/null +++ b/README.MD @@ -0,0 +1,9 @@ +# Intro + +This repository contains a simple web app and Playwright setup to give attendees the opportunity to get familiar with Playwrights features + +## Getting started + +1. `npm install` +2. `npm run build` +3. in a seperate shell: `npm run test` or `npx playwright test --ui` diff --git a/package-lock.json b/package-lock.json index 9a98287..b75c155 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,9 +28,9 @@ "tailwindcss": "3.4.1" }, "devDependencies": { - "@playwright/test": "^1.40.1", + "@axe-core/playwright": "^4.8.4", + "@playwright/test": "^1.41.2", "@types/node": "^20.11.16", - "axe-playwright": "^2.0.1", "eslint-plugin-playwright": "^0.22.2", "prisma": "5.8.1", "ts-node": "^10.9.2", @@ -56,6 +56,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@axe-core/playwright": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.8.4.tgz", + "integrity": "sha512-xpwd+T0BODt19hnXW0eX9xf+H/Ns1rdWwZNmuCV9UoTqjZ9mGm1F80pvh/A1r317ooltq8nwqcoVO9jbHWKSdA==", + "dev": true, + "dependencies": { + "axe-core": "~4.8.3" + }, + "peerDependencies": { + "playwright-core": ">= 1.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", @@ -457,12 +469,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.40.1.tgz", - "integrity": "sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", + "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==", "dev": true, "dependencies": { - "playwright": "1.40.1" + "playwright": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -610,12 +622,6 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" }, - "node_modules/@types/junit-report-builder": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", - "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==", - "dev": true - }, "node_modules/@types/node": { "version": "20.11.16", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", @@ -1037,45 +1043,13 @@ } }, "node_modules/axe-core": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", - "integrity": "sha512-zIURGIS1E1Q4pcrMjp+nnEh+16G56eG/MUllJH8yEvw7asDo7Ac9uhC9KIH5jzpITueEZolfYglnCGIuSBz39g==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.3.tgz", + "integrity": "sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw==", "engines": { "node": ">=4" } }, - "node_modules/axe-html-reporter": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/axe-html-reporter/-/axe-html-reporter-2.2.3.tgz", - "integrity": "sha512-io8aCEt4fJvv43W+33n3zEa8rdplH5Ti2v5fOnth3GBKLhLHarNs7jj46xGfpnGnpaNrz23/tXPHC3HbwTzwwA==", - "dev": true, - "dependencies": { - "mustache": "^4.0.1", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=8.9.0" - }, - "peerDependencies": { - "axe-core": ">=3" - } - }, - "node_modules/axe-playwright": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/axe-playwright/-/axe-playwright-2.0.1.tgz", - "integrity": "sha512-MHjNjGARulF9XzqSfspmNjw+tpBz4x9o1VlTuLWEUW9fqzhn+xWa1qEpuOIQPbsRWQiLfooDjQAunLeE0PM5AQ==", - "dev": true, - "dependencies": { - "@types/junit-report-builder": "^3.0.0", - "axe-core": "^4.5.1", - "axe-html-reporter": "2.2.3", - "junit-report-builder": "^3.0.1", - "picocolors": "^1.0.0" - }, - "peerDependencies": { - "playwright": ">1.0.0" - } - }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -1382,15 +1356,6 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, - "node_modules/date-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", - "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3122,21 +3087,6 @@ "node": ">=4.0" } }, - "node_modules/junit-report-builder": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-3.2.1.tgz", - "integrity": "sha512-IMCp5XyDQ4YESDE4Za7im3buM0/7cMnRfe17k2X8B05FnUl9vqnaliX6cgOEmPIeWKfJrEe/gANRq/XgqttCqQ==", - "dev": true, - "dependencies": { - "date-format": "4.0.3", - "lodash": "^4.17.21", - "make-dir": "^3.1.0", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -3189,12 +3139,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3222,30 +3166,6 @@ "node": ">=10" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -3312,15 +3232,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "bin": { - "mustache": "bin/mustache" - } - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -3815,12 +3726,12 @@ } }, "node_modules/playwright": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.40.1.tgz", - "integrity": "sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", "dev": true, "dependencies": { - "playwright-core": "1.40.1" + "playwright-core": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -3833,9 +3744,9 @@ } }, "node_modules/playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -5144,15 +5055,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "dev": true, - "engines": { - "node": ">=8.0" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index c824f25..cf33492 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "lint": "next lint", "postinstall": "prisma generate", "test": "playwright test", - "post": "prisma migrate reset --force" + "post": "prisma migrate reset --force", + "snapshot-update": " docker run -v ./:/workshop/ -it --rm --ipc=host mcr.microsoft.com/playwright:v1.41.1-jammy /bin/bash " }, "dependencies": { "@faker-js/faker": "^8.4.0", @@ -31,9 +32,9 @@ "tailwindcss": "3.4.1" }, "devDependencies": { - "@playwright/test": "^1.40.1", + "@playwright/test": "^1.41.2", "@types/node": "^20.11.16", - "axe-playwright": "^2.0.1", + "@axe-core/playwright": "^4.8.4", "eslint-plugin-playwright": "^0.22.2", "prisma": "5.8.1", "ts-node": "^10.9.2", diff --git a/playwright.config.ts b/playwright.config.ts index effe582..7e0edf5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -4,6 +4,7 @@ import { defineConfig, devices } from "@playwright/test"; */ export default defineConfig({ testDir: "./playwright/tests", + fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, @@ -13,26 +14,54 @@ export default defineConfig({ use: { baseURL: "http://localhost:3000", trace: "retain-on-failure", - headless: false, + // headless: !!process.env.CI, + headless: true, }, projects: [ + { name: "setup", testMatch: /.*\.setup\.ts/ }, { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, + name: "assignment1", + testDir: "./playwright/tests/assignment1", + }, + { + name: "assignment2", + testDir: "./playwright/tests/assignment2", + }, + { + name: "assignment3", + testDir: "./playwright/tests/assignment3", + dependencies: ["setup"], + use: { + storageState: "playwright/.auth/user.json", + }, + }, + { + name: "assignment4", + testDir: "./playwright/tests/assignment4", + dependencies: ["setup"], + use: { + storageState: "playwright/.auth/user.json", + }, + }, + { + name: "assignment5", + testDir: "./playwright/tests/assignment5", + }, + { + name: "assignment6", + testDir: "./playwright/tests/assignment6", + }, + { + name: "assignment7", + testDir: "./playwright/tests/assignment7", + dependencies: ["setup"], + use: { + storageState: "playwright/.auth/user.json", + }, }, - - // { - // name: "firefox", - // use: { ...devices["Desktop Firefox"] }, - // }, - - // { - // name: "webkit", - // use: { ...devices["Desktop Safari"] }, - // }, ], webServer: { - command: "npm run dev", + command: "npm run start", url: "http://127.0.0.1:3000", reuseExistingServer: !process.env.CI, }, diff --git a/playwright/support/fixtures/helpers.fixture.ts b/playwright/support/fixtures/helpers.fixture.ts new file mode 100644 index 0000000..873b763 --- /dev/null +++ b/playwright/support/fixtures/helpers.fixture.ts @@ -0,0 +1,10 @@ +import { test as baseTest } from "@playwright/test"; +import { Generator } from "../helpers/generator"; + +export const test = baseTest.extend<{ + generator: Generator; +}>({ + generator: async ({}, use) => { + await use(new Generator()); + }, +}); diff --git a/playwright/support/page-objects/index.ts b/playwright/support/fixtures/pageObjects.fixture.ts similarity index 56% rename from playwright/support/page-objects/index.ts rename to playwright/support/fixtures/pageObjects.fixture.ts index d72f14d..8d502b7 100644 --- a/playwright/support/page-objects/index.ts +++ b/playwright/support/fixtures/pageObjects.fixture.ts @@ -1,16 +1,14 @@ import { test as baseTest } from "@playwright/test"; -import { HomePage } from "./pages/home.page"; -import { AuthPage } from "./pages/auth.page"; -import { NewTodoPage } from "./pages/newTodo.page"; -import { UpdateTodoPage } from "./pages/updateTodo.page"; -import { AccountGenerator } from "../fixtures/accountGenerator.fixture"; +import { HomePage } from "../page-objects/pages/home.page"; +import { AuthPage } from "../page-objects/pages/auth.page"; +import { NewTodoPage } from "../page-objects/pages/newTodo.page"; +import { UpdateTodoPage } from "../page-objects/pages/updateTodo.page"; export const test = baseTest.extend<{ home: HomePage; auth: AuthPage; newTodo: NewTodoPage; updateTodo: UpdateTodoPage; - generator: AccountGenerator; }>({ home: async ({ page }, use) => { await use(new HomePage(page)); @@ -24,7 +22,4 @@ export const test = baseTest.extend<{ updateTodo: async ({ page }, use) => { await use(new UpdateTodoPage(page)); }, - generator: async ({}, use) => { - await use(new AccountGenerator()); - }, }); diff --git a/playwright/support/fixtures/resetRoutes.fixture.ts b/playwright/support/fixtures/resetRoutes.fixture.ts new file mode 100644 index 0000000..033ee91 --- /dev/null +++ b/playwright/support/fixtures/resetRoutes.fixture.ts @@ -0,0 +1,16 @@ +import { test as base } from "@playwright/test"; + +const response: any = global; +const user: any = global; + +export const test = base.extend<{ resetRoutes: void }>({ + resetRoutes: [ + async ({ page }, use: any): Promise => { + await use(); + await page.unrouteAll(); + }, + { auto: true }, + ], // starts automatically for every test - we pass "auto" for that. +}); + +export { expect } from "@playwright/test"; diff --git a/playwright/support/fixtures/test.fixture.ts b/playwright/support/fixtures/test.fixture.ts new file mode 100644 index 0000000..86aed4b --- /dev/null +++ b/playwright/support/fixtures/test.fixture.ts @@ -0,0 +1,6 @@ +import { mergeTests } from "@playwright/test"; +import { test as helpers } from "./helpers.fixture"; +import { test as pageObjects } from "./pageObjects.fixture"; +import { test as resetRoutes } from "./resetRoutes.fixture"; + +export const test = mergeTests(helpers, pageObjects, resetRoutes); diff --git a/playwright/support/helpers/generator.ts b/playwright/support/helpers/generator.ts new file mode 100644 index 0000000..206ac93 --- /dev/null +++ b/playwright/support/helpers/generator.ts @@ -0,0 +1,27 @@ +import { faker } from "@faker-js/faker"; + +export class Generator { + constructor() { + faker.seed(); + } + + public generateUser() { + return { + name: faker.person.firstName(), + email: faker.internet.email(), + password: faker.internet.password(), + }; + } + + public generateTodo() { + return { + title: faker.lorem.sentence(), + priority: faker.helpers.arrayElement(["Important", "Not Important"]), + }; + } +} + +export type todo = { + title: string; + priority: string; +}; diff --git a/playwright/support/page-objects/pages/newTodo.page.ts b/playwright/support/page-objects/pages/newTodo.page.ts index 6e13037..24ea0f2 100644 --- a/playwright/support/page-objects/pages/newTodo.page.ts +++ b/playwright/support/page-objects/pages/newTodo.page.ts @@ -25,7 +25,7 @@ export class NewTodoPage { async createNewTodo( title: string, importance = "Important" || "Not Important" - ) { + ): Promise { await this.nav.clickNewTodo(); await this.title.fill(title); await this.importance.selectOption({ label: importance }); @@ -34,6 +34,6 @@ export class NewTodoPage { ); await this.create.click(); const response = await (await responsePromise).json(); - return response; + return response.id; } } diff --git a/playwright/tests/assignment1/assignment1.spec.ts b/playwright/tests/assignment1/assignment1.spec.ts new file mode 100644 index 0000000..3f9d2b6 --- /dev/null +++ b/playwright/tests/assignment1/assignment1.spec.ts @@ -0,0 +1,49 @@ +import { Generator } from "../../support/helpers/generator"; +import { test } from "../../support/fixtures/test.fixture"; +import { expect } from "@playwright/test"; + +test("Assignment 1A", async ({ page }) => { + const generator = new Generator(); + const user = generator.generateUser(); + + await page.goto( + "http://localhost:3000/auth?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F" + ); + await page.getByText("Sign Up").click(); + await page.getByPlaceholder("name").click(); + await page.getByPlaceholder("name").fill(user.name); + await page.getByPlaceholder("name").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("email@gmail.com").fill(user.email); + await page.getByPlaceholder("email@gmail.com").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("********").fill(user.password); + await page.getByLabel("Sign Up").click(); + await page.getByPlaceholder("email@gmail.com").click(); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("email@gmail.com").fill(user.email); + await page.getByPlaceholder("email@gmail.com").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("********").fill(user.password); + await page.getByTestId("auth-page").getByLabel("Sign In").click(); + await expect(page.getByRole("paragraph")).toContainText("No Todos found."); +}); + +test("Assignment 1B", async ({ page }) => { + const generator = new Generator(); + const user = generator.generateUser(); + + await page.goto( + "http://localhost:3000/auth?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F" + ); + await page.getByText("Sign Up").click(); + await page.getByPlaceholder("name").click(); + await page.getByPlaceholder("name").fill(user.name); + await page.getByPlaceholder("name").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("email@gmail.com").fill(user.email); + await page.getByPlaceholder("email@gmail.com").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("********").fill(user.password); + await page.getByLabel("Sign Up").click(); + await page.getByPlaceholder("email@gmail.com").click(); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("email@gmail.com").fill(user.email); + await page.getByPlaceholder("email@gmail.com").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("********").fill(user.password); + await page.getByTestId("auth-page").getByLabel("Sign In").click(); + await expect(page.getByRole("paragraph")).toContainText("No Todos found."); +}); diff --git a/playwright/tests/assignment2/assignment2.spec.ts b/playwright/tests/assignment2/assignment2.spec.ts new file mode 100644 index 0000000..822baa1 --- /dev/null +++ b/playwright/tests/assignment2/assignment2.spec.ts @@ -0,0 +1,28 @@ +import { test } from "../../support/fixtures/test.fixture"; +import { expect } from "@playwright/test"; + +test("Assignment 2", async ({ page }) => { + await page.goto( + "http://localhost:3000/auth?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F" + ); + await page.getByText("Sign Up").click(); + await page.getByPlaceholder("name").click(); + await page.getByPlaceholder("name").fill("testy@testy.nl"); + await page.getByPlaceholder("name").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("email@gmail.com").fill("test@testy.nl"); + await page.getByPlaceholder("email@gmail.com").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("********").fill("test"); + + // Set up Mocking First, then press Sign Up + await page.route("**/api/register", (route) => { + route.fulfill({ status: 409 }); + }); + + await page.getByLabel("Sign Up").click(); + + await expect( + page + .getByRole("alert") + .filter({ has: page.getByText("User Already exists") }) + ).toBeVisible(); +}); diff --git a/playwright/tests/assignment3/assignment3.spec.ts b/playwright/tests/assignment3/assignment3.spec.ts new file mode 100644 index 0000000..c6e383a --- /dev/null +++ b/playwright/tests/assignment3/assignment3.spec.ts @@ -0,0 +1,9 @@ +import { test } from "../../support/fixtures/test.fixture"; +import { expect } from "@playwright/test"; + +test("Assignment 3", async ({ page }) => { + await page.goto( + "http://localhost:3000/auth?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F" + ); + await expect(page.getByRole("paragraph")).toContainText("No Todos found."); +}); diff --git a/playwright/tests/assignment4/assignment4.spec.ts b/playwright/tests/assignment4/assignment4.spec.ts new file mode 100644 index 0000000..5fbc0e4 --- /dev/null +++ b/playwright/tests/assignment4/assignment4.spec.ts @@ -0,0 +1,38 @@ +import { test } from "../../support/fixtures/test.fixture"; +import { expect } from "@playwright/test"; +import type { todo } from "../../support/helpers/generator"; + +let generatedTodo: todo; +let todoId: string; + +test("Assignment 4", async ({ page, newTodo, generator, home }) => { + await test.step("Generate new todo", async () => { + generatedTodo = generator.generateTodo(); + }); + + await test.step("Open Todo app", async () => { + await page.goto("/"); + await expect(page.getByRole("paragraph")).toContainText("No Todos found."); + }); + + await test.step("Create new todo", async () => { + todoId = await newTodo.createNewTodo( + generatedTodo.title, + generatedTodo.priority + ); + }); + + await test.step("Verify todo is created", async () => { + await expect( + await home.todoContainer.todoItemComponent.getTodo(todoId) + ).toHaveText(generatedTodo.title); + }); + + await test.step("Delete todo", async () => { + await home.todoContainer.todoItemComponent.deleteTodo(todoId); + }); + + await test.step("Verify todo is deleted", async () => { + await expect(page.getByRole("paragraph")).toContainText("No Todos found."); + }); +}); diff --git a/playwright/tests/assignment6/assignment6.spec.ts b/playwright/tests/assignment6/assignment6.spec.ts new file mode 100644 index 0000000..ae66a05 --- /dev/null +++ b/playwright/tests/assignment6/assignment6.spec.ts @@ -0,0 +1,31 @@ +import path from "path"; +import { test } from "../../support/fixtures/test.fixture"; +import { expect } from "@playwright/test"; + +test("Assignment 6A", async ({ page }) => { + await test.step("Open Todo app", async () => { + await page.goto("/"); + }); + await test.step("Should match screenshot", async () => { + await expect(page).toHaveScreenshot("test6a.png", { + mask: [page.getByAltText("Gibli")], + }); + }); +}); + +/* +Use the display: none; property. This will completely hide the image from the user. +Use the visibility: hidden; property. This will hide the image from the user, but it will still take up space on the page. +*/ + +test("Assignment 6B", async ({ page }) => { + await test.step("Open Todo app", async () => { + await page.goto("/"); + }); + await test.step("Should match screenshot", async () => { + await expect(page).toHaveScreenshot("test6b.png", { + stylePath: path.join(__dirname, "mask.css"), + }); + }); +}); +//https://github.com/microsoft/playwright/issues/29249 need to add file name for screenshot diff --git a/playwright/tests/assignment6/assignment6.spec.ts-snapshots/test6a-assignment6-linux.png b/playwright/tests/assignment6/assignment6.spec.ts-snapshots/test6a-assignment6-linux.png new file mode 100644 index 0000000..a79b17c Binary files /dev/null and b/playwright/tests/assignment6/assignment6.spec.ts-snapshots/test6a-assignment6-linux.png differ diff --git a/playwright/tests/assignment6/assignment6.spec.ts-snapshots/test6b-assignment6-linux.png b/playwright/tests/assignment6/assignment6.spec.ts-snapshots/test6b-assignment6-linux.png new file mode 100644 index 0000000..9448e37 Binary files /dev/null and b/playwright/tests/assignment6/assignment6.spec.ts-snapshots/test6b-assignment6-linux.png differ diff --git a/playwright/tests/assignment6/mask.css b/playwright/tests/assignment6/mask.css new file mode 100644 index 0000000..f9c647c --- /dev/null +++ b/playwright/tests/assignment6/mask.css @@ -0,0 +1,3 @@ +img, video { + visibility: hidden; +} \ No newline at end of file diff --git a/playwright/tests/assignment7/assignment7.spec.ts b/playwright/tests/assignment7/assignment7.spec.ts new file mode 100644 index 0000000..6ee2eb1 --- /dev/null +++ b/playwright/tests/assignment7/assignment7.spec.ts @@ -0,0 +1,33 @@ +import { test } from "../../support/fixtures/test.fixture"; +import { expect } from "@playwright/test"; +import type { todo } from "../../support/helpers/generator"; +import AxeBuilder from "@axe-core/playwright"; // 1 + +let firstTodo: todo; +let secondTodo: todo; +let thirdTodo: todo; + +test("Assignment 7", async ({ page, newTodo, generator }) => { + await test.step("Generate new todo", async () => { + firstTodo = generator.generateTodo(); + secondTodo = generator.generateTodo(); + thirdTodo = generator.generateTodo(); + }); + + await test.step("Open Todo app", async () => { + await page.goto("/"); + await expect(page.getByRole("paragraph")).toContainText("No Todos found."); + }); + + await test.step("Create new todos", async () => { + await newTodo.createNewTodo(firstTodo.title, firstTodo.priority); + await newTodo.createNewTodo(secondTodo.title, secondTodo.priority); + await newTodo.createNewTodo(thirdTodo.title, thirdTodo.priority); + }); + + await test.step("Run Axe accesability scan", async () => { + const accessibilityScanResults = await new AxeBuilder({ page }).analyze(); // 4 + //console.log(accessibilityScanResults); + expect(accessibilityScanResults.violations).toEqual([]); // 5 + }); +}); diff --git a/playwright/tests/setup/authenticate.setup.ts b/playwright/tests/setup/authenticate.setup.ts new file mode 100644 index 0000000..8b2cb2b --- /dev/null +++ b/playwright/tests/setup/authenticate.setup.ts @@ -0,0 +1,31 @@ +import { Generator } from "../../support/helpers/generator"; +import { + expect, + test as setup, +} from "../../support/fixtures/resetRoutes.fixture"; + +const authFile = "playwright/.auth/user.json"; + +setup("authenticate", async ({ page }) => { + const generator = new Generator(); + const user = generator.generateUser(); + + await page.goto( + "http://localhost:3000/auth?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F" + ); + await page.getByText("Sign Up").click(); + await page.getByPlaceholder("name").click(); + await page.getByPlaceholder("name").fill(user.name); + await page.getByPlaceholder("name").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("email@gmail.com").fill(user.email); + await page.getByPlaceholder("email@gmail.com").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("********").fill(user.password); + await page.getByLabel("Sign Up").click(); + await page.getByPlaceholder("email@gmail.com").click(); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("email@gmail.com").fill(user.email); + await page.getByPlaceholder("email@gmail.com").press("Tab"); // This is not strictly needed, but is generated by the recorder + await page.getByPlaceholder("********").fill(user.password); + await page.getByTestId("auth-page").getByLabel("Sign In").click(); + await expect(page.getByRole("paragraph")).toContainText("No Todos found."); + await page.context().storageState({ path: authFile }); +}); diff --git a/prisma/dev.db b/prisma/dev.db index d9e159c..b6e551e 100644 Binary files a/prisma/dev.db and b/prisma/dev.db differ diff --git a/tsconfig.json b/tsconfig.json index 51d0503..9d23c7b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "types": ["axe-playwright", "node"], + "types": ["node"], "target": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true,