Using Playwright for e2e testing

This commit is contained in:
aronmal 2023-07-27 11:02:54 +02:00
parent b407553f0d
commit eb8aee090f
Signed by: aronmal
GPG key ID: 816B7707426FC612
16 changed files with 328 additions and 2608 deletions

27
.github/workflows/playwright.yml vendored Normal file
View file

@ -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@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: 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@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

View file

@ -1,7 +1,5 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
__tests__/screenshots/*
# logs
/log
@ -43,3 +41,8 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# playwright
/test-results/
/playwright-report/
/playwright/.cache/

View file

@ -1,83 +0,0 @@
describe("Check Azure AD auth", () => {
const callbackUrl = process.env.NEXTAUTH_URL + "/"
it("Login process...", async () => {
let redirected = false
let thirdParty = false
page.on("framenavigated", (frame) => {
if (redirected) return
const frameUrl = frame.url()
// console.log("Window Location Changed:", frameUrl)
if (frameUrl === callbackUrl) redirected = true
})
try {
await page.goto(callbackUrl + "signin")
await page.waitForSelector("#microsoft")
await page.click("#microsoft")
thirdParty = true
await page.waitForNavigation()
await page.waitForSelector('input[type="email"]')
const emailInput = await page.$('input[type="email"]')
await emailInput.type(process.env.AUTH_EMAIL)
await page.waitForSelector('input[value="Next"]')
const nextInput = await page.$('input[value="Next"]')
await nextInput.click()
await page.waitForSelector('input[type="password"]')
const passwordInput = await page.$('input[type="password"]')
await passwordInput.type(process.env.AUTH_PW)
await page.waitForSelector('input[value="Sign in"]')
const signinInput = await page.$('input[value="Sign in"]')
await signinInput.click()
await page.waitForSelector('input[value="No"]')
const noInput = await page.$('input[value="No"]')
await noInput.click()
await page.waitForFunction(`window.location.href === '${callbackUrl}'`)
} catch (e) {
if (!redirected || thirdParty) throw e
}
}, 60000)
it("Is logged in", async () => {
await page.goto(callbackUrl + "signin")
await page.waitForFunction(`window.location.href === '${callbackUrl}'`)
}, 30000)
it("Is logged out", async () => {
await page.goto(
"https://login.microsoftonline.com/common/oauth2/v2.0/logout",
)
await page.waitForSelector(`div[data-test-id="${process.env.AUTH_EMAIL}"]`)
const signoutDiv = await page.$(
`div[data-test-id="${process.env.AUTH_EMAIL}"]`,
)
await signoutDiv.click()
await page.waitForFunction(
`window.location.href === 'https://login.microsoftonline.com/common/oauth2/v2.0/logoutsession'`,
)
// Wait for the element to be visible in the page
await page.waitForSelector("#login_workload_logo_text")
// Get the element handle
const elementHandle = await page.$("#login_workload_logo_text")
// Get the inner text of the element
const innerText = await page.evaluate(
(element) => element.innerText,
elementHandle,
)
// Assert that the inner text matches the expected text
expect(innerText.trim()).toBe("You signed out of your account")
}, 30000)
})

View file

@ -1,57 +0,0 @@
const { PrismaClient } = require("@prisma/client")
const prisma = new PrismaClient()
const { createHash, randomBytes } = require("crypto")
describe("Check Email auth", () => {
const callbackUrl = process.env.NEXTAUTH_URL + "/"
const player1Email = "player1@example.com"
it("Email login process...", async () => {
await page.goto(callbackUrl + "signin")
await page.waitForSelector('input[type="email"]')
const emailInput = await page.$('input[type="email"]')
await emailInput.type(player1Email)
await page.click('button[type="submit"]')
await page.waitForFunction(
`window.location.href === '${callbackUrl}api/auth/verify-request?provider=email&type=email'`,
)
}, 30000)
it("Verify Email...", async () => {
const token = randomBytes(32).toString("hex")
const hash = createHash("sha256")
// Prefer provider specific secret, but use default secret if none specified
.update(`${token}${process.env.NEXTAUTH_SECRET}`)
.digest("hex")
// Use Prisma to fetch the latest token for the email
const latestToken = await prisma.VerificationToken.findFirst({
where: { identifier: player1Email },
orderBy: { expires: "desc" },
})
await prisma.VerificationToken.update({
where: {
identifier_token: {
identifier: player1Email,
token: latestToken.token,
},
},
data: { token: hash },
})
const params = new URLSearchParams({
callbackUrl,
token,
email: player1Email,
})
const url = callbackUrl + "api/auth/callback/email?" + params
await page.goto(url)
await page.waitForFunction(`window.location.href === '${callbackUrl}'`)
}, 30000)
})

View file

@ -1,27 +0,0 @@
#!/bin/bash
# Build the project
pnpm run build
# Function to kill the server process
function kill_server {
local server_pid=$(lsof -i :3000 -t)
if [[ -n $server_pid ]]; then
echo "Killing server..." $server_pid
kill -15 $server_pid
fi
}
# Function to run the tests
function run_tests {
pnpm test
}
# Start the server in the background
pnpm run start &
# Capture exit signals and execute the kill_server function
trap kill_server EXIT ERR
# Run the tests
run_tests

View file

@ -0,0 +1,85 @@
import { expect, test, type BrowserContext, type Page } from "@playwright/test"
const callbackUrl = process.env.NEXTAUTH_URL + "/"
let context: BrowserContext
let page: Page
test.describe.serial("Check Azure AD auth", () => {
test.beforeAll(async ({ browser }) => {
context = await browser.newContext()
page = await context.newPage()
})
test.afterAll(async () => {
await context.close()
})
test("Login process...", async ({ browser }) => {
await page.goto(callbackUrl + "signin")
await page
.getByRole("button", { name: "Microsoft_icon Sign in with Microsoft" })
.click()
const emailLocator = page.locator(
`[data-test-id="${process.env.AUTH_EMAIL ?? ""}"]`,
)
if (await emailLocator.isVisible()) {
await emailLocator.click()
await new Promise((resolve) => setTimeout(resolve, 1000))
} else {
console.log(
"The email locator is not present on the page. Skipping this step.",
)
// Optionally, you can throw an error, fail the test, or take any other desired action here.
}
await page
.getByLabel("someone@example.com")
.fill(process.env.AUTH_EMAIL ?? "")
await page.getByRole("button", { name: "Next" }).click()
await new Promise((resolve) => setTimeout(resolve, 1000))
await page.getByLabel("Password").fill(process.env.AUTH_PW ?? "")
await page.getByRole("button", { name: "Sign in" }).click()
await new Promise((resolve) => setTimeout(resolve, 1000))
await page.getByRole("button", { name: "No" }).click({})
await new Promise((resolve) => setTimeout(resolve, 1000))
await page.waitForURL(
callbackUrl + (browser.browserType().name() === "webkit" ? "#" : ""),
)
})
test("Is logged in", async () => {
await page.goto(callbackUrl + "signin")
await page.waitForURL(callbackUrl)
expect(await page.screenshot()).toMatchSnapshot("1.png")
})
test("Is logged out", async () => {
await page.goto(
"https://login.microsoftonline.com/common/oauth2/v2.0/logout",
)
await page.waitForLoadState("domcontentloaded")
const emailLocator = page.locator(
`[data-test-id="${process.env.AUTH_EMAIL ?? ""}"]`,
)
if (await emailLocator.isVisible()) {
await emailLocator.click()
} else {
console.log(
"The email locator is not present on the page. Skipping this step.",
)
// Optionally, you can throw an error, fail the test, or take any other desired action here.
}
await page
.getByRole("heading", { name: "You signed out of your account" })
.click()
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -0,0 +1,72 @@
import {
test,
type Browser,
type BrowserContext,
type Page,
} from "@playwright/test"
import { createHash, randomBytes } from "crypto"
import prisma from "../lib/prisma"
const callbackUrl = process.env.NEXTAUTH_URL + "/"
const player1Email = (browser: Browser) =>
browser.browserType().name() + "-player-1@example.com"
let context: BrowserContext
let page: Page
test.describe.serial("Check Email auth", () => {
test.beforeAll(async ({ browser }) => {
context = await browser.newContext()
page = await context.newPage()
})
test.afterAll(async () => {
await context.close()
})
test("Email login process...", async ({ browser }) => {
await page.goto(callbackUrl + "signin")
await page.getByPlaceholder("user@example.com").fill(player1Email(browser))
await page.getByRole("button", { name: "Sign in with Email" }).click()
await page.waitForURL(
callbackUrl + "api/auth/verify-request?provider=email&type=email",
)
})
test("Verify Email...", async ({ browser }) => {
const token = randomBytes(32).toString("hex")
const hash = createHash("sha256")
// Prefer provider specific secret, but use default secret if none specified
.update(`${token}${process.env.NEXTAUTH_SECRET}`)
.digest("hex")
// Use Prisma to fetch the latest token for the email
const latestToken = await prisma.verificationToken.findFirst({
where: { identifier: player1Email(browser) },
orderBy: { expires: "desc" },
})
await prisma.verificationToken.update({
where: {
identifier_token: {
identifier: player1Email(browser),
token: latestToken?.token ?? "",
},
},
data: { token: hash },
})
const params = new URLSearchParams({
callbackUrl,
token,
email: player1Email(browser),
})
const url = callbackUrl + "api/auth/callback/email?" + params
await page.goto(url)
await page.waitForLoadState("domcontentloaded")
})
})

View file

@ -1,5 +0,0 @@
module.exports = {
verbose: true,
preset: "jest-puppeteer",
setupFiles: ["dotenv/config"],
}

5
leaky-ships/kill-server.sh Executable file
View file

@ -0,0 +1,5 @@
server_pid=$(lsof -i :3000 -t)
if [[ -n $server_pid ]]; then
echo "Killing server..." $server_pid
kill -9 $server_pid
fi

View file

@ -7,8 +7,7 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest -w 1",
"build-start-test": "./build-start-test.sh"
"test": "pnpm playwright test --ui"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0",
@ -46,6 +45,7 @@
"zustand": "^4.3.9"
},
"devDependencies": {
"@playwright/test": "^1.36.2",
"@total-typescript/ts-reset": "^0.3.7",
"@types/node": "^18.17.0",
"@types/react": "^18.2.15",
@ -54,8 +54,6 @@
"autoprefixer": "^10.4.14",
"dotenv": "^16.3.1",
"eslint-config-prettier": "^8.8.0",
"jest": "^29.6.1",
"jest-puppeteer": "^9.0.0",
"postcss": "^8.4.27",
"prettier": "^3.0.0",
"prettier-plugin-organize-imports": "^3.2.3",

View file

@ -11,7 +11,7 @@ export default function Home() {
<Logo />
<BurgerMenu />
<div className="flex h-36 w-64 items-center justify-center overflow-hidden rounded-xl border-8 border-black bg-[#2227] sm:h-48 sm:w-96 md:h-72 md:w-[32rem] md:border-[6px] xl:h-[26rem] xl:w-[48rem]">
<video controls>
<video controls preload="metadata">
<source src="/Regelwerk.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>

View file

@ -0,0 +1,77 @@
import { defineConfig, devices } from "@playwright/test"
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
require("dotenv").config()
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./e2e",
/* 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: "pnpm run start",
url: process.env.NEXTAUTH_URL,
reuseExistingServer: !process.env.CI,
},
})

File diff suppressed because it is too large Load diff