Merged in feature/add-login-test-2 (pull request #3261)
e2e tests for my pages * feature/add-e2e-tests-for-mypages * remove unneccesary awaits Approved-by: Linus Flood
This commit is contained in:
3
apps/scandic-web/.gitignore
vendored
3
apps/scandic-web/.gitignore
vendored
@@ -62,3 +62,6 @@ variables.css
|
|||||||
.swc
|
.swc
|
||||||
|
|
||||||
public/_static/shared
|
public/_static/shared
|
||||||
|
|
||||||
|
test-results
|
||||||
|
playwright-report
|
||||||
@@ -10,7 +10,9 @@
|
|||||||
"lint:fix": "next typegen && eslint --fix --max-warnings 0 . && tsgo --noEmit",
|
"lint:fix": "next typegen && eslint --fix --max-warnings 0 . && tsgo --noEmit",
|
||||||
"start": "node .next/standalone/server.js",
|
"start": "node .next/standalone/server.js",
|
||||||
"test:setup": "yarn build && yarn start",
|
"test:setup": "yarn build && yarn start",
|
||||||
"preinstall": "/bin/sh -c \"export $(cat .env.local | grep -v '^#' | xargs)\"",
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
"test:e2e:debug": "playwright test --debug",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"ci:build": "yarn lint && yarn test && yarn build",
|
"ci:build": "yarn lint && yarn test && yarn build",
|
||||||
@@ -91,6 +93,7 @@
|
|||||||
"@eslint/js": "^9.26.0",
|
"@eslint/js": "^9.26.0",
|
||||||
"@formatjs/cli": "^6.7.1",
|
"@formatjs/cli": "^6.7.1",
|
||||||
"@lokalise/node-api": "^14.0.0",
|
"@lokalise/node-api": "^14.0.0",
|
||||||
|
"@playwright/test": "^1.57.0",
|
||||||
"@react-aria/test-utils": "1.0.0-alpha.8",
|
"@react-aria/test-utils": "1.0.0-alpha.8",
|
||||||
"@scandic-hotels/typescript-config": "workspace:*",
|
"@scandic-hotels/typescript-config": "workspace:*",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
|
|||||||
95
apps/scandic-web/playwright.config.ts
Normal file
95
apps/scandic-web/playwright.config.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
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",
|
||||||
|
timeout: 120_000,
|
||||||
|
/* 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",
|
||||||
|
|
||||||
|
/* How long to wait for actions to complete. */
|
||||||
|
actionTimeout: 60_000,
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: process.env.CI ? "on-first-retry" : "retain-on-failure",
|
||||||
|
},
|
||||||
|
expect: {
|
||||||
|
timeout: 60_000,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
viewport: { width: 1400, height: 720 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "firefox",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Firefox"],
|
||||||
|
viewport: { width: 1400, height: 720 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "webkit",
|
||||||
|
use: {
|
||||||
|
...devices["Desktop Safari"],
|
||||||
|
viewport: { width: 1400, height: 720 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/* 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: "yarn dev",
|
||||||
|
url: "http://localhost:3000",
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
/* eslint-disable formatjs/no-literal-string-in-jsx */
|
|
||||||
|
|
||||||
import { User } from "@react-aria/test-utils"
|
|
||||||
import { userEvent } from "@testing-library/user-event"
|
|
||||||
import { FormProvider, useForm } from "react-hook-form"
|
|
||||||
import { afterEach, describe, expect, test } from "vitest"
|
|
||||||
|
|
||||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
|
||||||
import { dt } from "@scandic-hotels/common/dt"
|
|
||||||
import { getLocalizedMonthName } from "@scandic-hotels/common/utils/dateFormatting"
|
|
||||||
import Date from "@scandic-hotels/design-system/Form/Date"
|
|
||||||
|
|
||||||
import { cleanup, render, screen } from "@/tests/utils"
|
|
||||||
|
|
||||||
const testUtilUser = new User({ interactionType: "touch" })
|
|
||||||
|
|
||||||
interface FormWrapperProps {
|
|
||||||
defaultValues: Record<string, unknown>
|
|
||||||
children: React.ReactNode
|
|
||||||
onSubmit: (event: unknown) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function FormWrapper({ defaultValues, children, onSubmit }: FormWrapperProps) {
|
|
||||||
const methods = useForm({
|
|
||||||
defaultValues,
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<FormProvider {...methods}>
|
|
||||||
<form onSubmit={methods.handleSubmit(onSubmit)}>
|
|
||||||
{children}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
</FormProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectOption(name: string, value: string) {
|
|
||||||
const selectTester = testUtilUser.createTester("Select", {
|
|
||||||
root: screen.getByTestId(name),
|
|
||||||
interactionType: "touch",
|
|
||||||
})
|
|
||||||
|
|
||||||
selectTester.selectOption({ option: value })
|
|
||||||
}
|
|
||||||
|
|
||||||
const testCases = [
|
|
||||||
{
|
|
||||||
description: "date is set and submitted successfully",
|
|
||||||
defaultValue: "",
|
|
||||||
dateOfBirth: "1987-12-05",
|
|
||||||
expectedOutput: {
|
|
||||||
dateOfBirth: "1987-12-05",
|
|
||||||
year: 1987,
|
|
||||||
month: 12,
|
|
||||||
day: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "sets default value and submits successfully",
|
|
||||||
defaultValue: "2000-01-01",
|
|
||||||
dateOfBirth: "",
|
|
||||||
expectedOutput: {
|
|
||||||
dateOfBirth: "2000-01-01",
|
|
||||||
year: 2000,
|
|
||||||
month: 1,
|
|
||||||
day: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "accepts date exactly 18 years old",
|
|
||||||
defaultValue: "",
|
|
||||||
dateOfBirth: dt().subtract(18, "year").format("YYYY-MM-DD"),
|
|
||||||
expectedOutput: {
|
|
||||||
dateOfBirth: dt().subtract(18, "year").format("YYYY-MM-DD"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "rejects date below 18 years old - by year",
|
|
||||||
defaultValue: "",
|
|
||||||
dateOfBirth: dt().subtract(17, "year").format("YYYY-MM-DD"),
|
|
||||||
expectedOutput: {
|
|
||||||
dateOfBirth: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "rejects date below 18 years old - by month",
|
|
||||||
defaultValue: "",
|
|
||||||
dateOfBirth: dt().subtract(18, "year").add(1, "month").format("YYYY-MM-DD"),
|
|
||||||
expectedOutput: {
|
|
||||||
dateOfBirth: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
description: "rejects date below 18 years old - by day",
|
|
||||||
defaultValue: "",
|
|
||||||
dateOfBirth: dt().subtract(18, "year").add(1, "day").format("YYYY-MM-DD"),
|
|
||||||
expectedOutput: {
|
|
||||||
dateOfBirth: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
describe.skip("Date input", () => {
|
|
||||||
afterEach(cleanup)
|
|
||||||
|
|
||||||
test.each(testCases)(
|
|
||||||
"$description",
|
|
||||||
async ({ defaultValue, dateOfBirth, expectedOutput }) => {
|
|
||||||
const user = userEvent.setup()
|
|
||||||
|
|
||||||
render(
|
|
||||||
<FormWrapper
|
|
||||||
defaultValues={{ dateOfBirth: defaultValue }}
|
|
||||||
onSubmit={(data) => {
|
|
||||||
expect(data).toEqual(expectedOutput)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Date
|
|
||||||
labels={{
|
|
||||||
day: "day",
|
|
||||||
month: "Month",
|
|
||||||
year: "Year",
|
|
||||||
errorMessage: "Date is required",
|
|
||||||
}}
|
|
||||||
lang={Lang.en}
|
|
||||||
name="dateOfBirth"
|
|
||||||
/>
|
|
||||||
</FormWrapper>
|
|
||||||
)
|
|
||||||
|
|
||||||
const date = dt(dateOfBirth).toDate()
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = date.getMonth() + 1
|
|
||||||
const day = date.getDate()
|
|
||||||
|
|
||||||
selectOption("year", year.toString())
|
|
||||||
selectOption("month", getLocalizedMonthName(month, Lang.en))
|
|
||||||
selectOption("day", day.toString())
|
|
||||||
|
|
||||||
const submitButton = screen.getByRole("button", { name: /submit/i })
|
|
||||||
await user.click(submitButton)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
44
apps/scandic-web/tests/loginUtil.ts
Normal file
44
apps/scandic-web/tests/loginUtil.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { expect, type Page } from "@playwright/test"
|
||||||
|
|
||||||
|
const AUTH_URL_REGEX =
|
||||||
|
/https:\/\/.*\.scandichotels\.com\/authn\/authenticate\/scandic/i
|
||||||
|
|
||||||
|
export async function performLogin({
|
||||||
|
page,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
}: {
|
||||||
|
page: Page
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
}) {
|
||||||
|
await page.goto("/en")
|
||||||
|
const loginLink = page.getByRole("link", { name: /log in\/join/i })
|
||||||
|
await expect(loginLink).not.toBeDisabled()
|
||||||
|
|
||||||
|
await loginLink.click()
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(AUTH_URL_REGEX)
|
||||||
|
|
||||||
|
// Fill in the login form
|
||||||
|
|
||||||
|
const usernameTextBox = page.getByRole("textbox", {
|
||||||
|
name: /email \/ membership number/i,
|
||||||
|
})
|
||||||
|
await usernameTextBox.fill(username)
|
||||||
|
|
||||||
|
const passwordTextBox = page.getByRole("textbox", { name: /password/i })
|
||||||
|
await passwordTextBox.fill(password)
|
||||||
|
|
||||||
|
const signInButton = page.getByRole("button", { name: /log in/i })
|
||||||
|
await signInButton.click()
|
||||||
|
|
||||||
|
const profileButton = await getProfileButton(page)
|
||||||
|
await expect(profileButton).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProfileButton(page: Page) {
|
||||||
|
return page.getByRole("button", {
|
||||||
|
name: /^\p{L}{2} hi \p{L}+!/iu,
|
||||||
|
})
|
||||||
|
}
|
||||||
22
apps/scandic-web/tests/profile/mybenefits.spec.ts
Normal file
22
apps/scandic-web/tests/profile/mybenefits.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { expect, test } from "@playwright/test"
|
||||||
|
|
||||||
|
import { getProfileButton, performLogin } from "../loginUtil"
|
||||||
|
import { testData } from "../testdata"
|
||||||
|
|
||||||
|
test("can navigate to my benefits page", async ({ page }) => {
|
||||||
|
await performLogin({
|
||||||
|
page,
|
||||||
|
username: testData.basicUser.membershipId,
|
||||||
|
password: testData.basicUser.password,
|
||||||
|
})
|
||||||
|
|
||||||
|
const profileButton = await getProfileButton(page)
|
||||||
|
profileButton.click()
|
||||||
|
const myStaysLink = page.getByRole("link", { name: /my benefits/i })
|
||||||
|
await myStaysLink.click()
|
||||||
|
|
||||||
|
const currentPerksAndBenefits = page.getByRole("heading", {
|
||||||
|
name: /current perks and benefits/i,
|
||||||
|
})
|
||||||
|
await expect(currentPerksAndBenefits).toBeVisible()
|
||||||
|
})
|
||||||
20
apps/scandic-web/tests/profile/mypoints.spec.ts
Normal file
20
apps/scandic-web/tests/profile/mypoints.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { expect, test } from "@playwright/test"
|
||||||
|
|
||||||
|
import { getProfileButton, performLogin } from "../loginUtil"
|
||||||
|
import { testData } from "../testdata"
|
||||||
|
|
||||||
|
test("can navigate to my points page", async ({ page }) => {
|
||||||
|
await performLogin({
|
||||||
|
page,
|
||||||
|
username: testData.basicUser.membershipId,
|
||||||
|
password: testData.basicUser.password,
|
||||||
|
})
|
||||||
|
|
||||||
|
const profileButton = await getProfileButton(page)
|
||||||
|
profileButton.click()
|
||||||
|
const myPointsLink = page.getByRole("link", { name: /my points/i })
|
||||||
|
await myPointsLink.click()
|
||||||
|
|
||||||
|
const pointsToSpend = page.getByRole("heading", { name: /points to spend/i })
|
||||||
|
await expect(pointsToSpend).toBeVisible()
|
||||||
|
})
|
||||||
22
apps/scandic-web/tests/profile/mystays.spec.ts
Normal file
22
apps/scandic-web/tests/profile/mystays.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { expect, test } from "@playwright/test"
|
||||||
|
|
||||||
|
import { getProfileButton, performLogin } from "../loginUtil"
|
||||||
|
import { testData } from "../testdata"
|
||||||
|
|
||||||
|
test("can navigate to my stays page", async ({ page }) => {
|
||||||
|
await performLogin({
|
||||||
|
page,
|
||||||
|
username: testData.basicUser.membershipId,
|
||||||
|
password: testData.basicUser.password,
|
||||||
|
})
|
||||||
|
|
||||||
|
const profileButton = await getProfileButton(page)
|
||||||
|
profileButton.click()
|
||||||
|
const myStaysLink = page.getByRole("link", { name: /my stays/i })
|
||||||
|
await myStaysLink.click()
|
||||||
|
|
||||||
|
const upcomingStays = page.getByRole("heading", {
|
||||||
|
name: /^upcoming stays$/i,
|
||||||
|
})
|
||||||
|
await expect(upcomingStays).toBeVisible()
|
||||||
|
})
|
||||||
23
apps/scandic-web/tests/profile/overview.spec.ts
Normal file
23
apps/scandic-web/tests/profile/overview.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { expect, test } from "@playwright/test"
|
||||||
|
|
||||||
|
import { getProfileButton, performLogin } from "../loginUtil"
|
||||||
|
import { testData } from "../testdata"
|
||||||
|
|
||||||
|
test("can navigate to overview page", async ({ page }) => {
|
||||||
|
await performLogin({
|
||||||
|
page,
|
||||||
|
username: testData.basicUser.membershipId,
|
||||||
|
password: testData.basicUser.password,
|
||||||
|
})
|
||||||
|
|
||||||
|
const profileButton = await getProfileButton(page)
|
||||||
|
profileButton.click()
|
||||||
|
const overviewLink = page.getByRole("link", { name: /overview/i })
|
||||||
|
await overviewLink.click()
|
||||||
|
|
||||||
|
const scandicLevel = page.getByText(/level \d+/i)
|
||||||
|
await expect(scandicLevel).toBeVisible()
|
||||||
|
|
||||||
|
const membershipId = page.getByText(testData.basicUser.membershipId)
|
||||||
|
await expect(membershipId).toBeVisible()
|
||||||
|
})
|
||||||
8
apps/scandic-web/tests/testdata.ts
Normal file
8
apps/scandic-web/tests/testdata.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const testData = {
|
||||||
|
basicUser: {
|
||||||
|
membershipId: "30812500328010",
|
||||||
|
password: "Test@12345",
|
||||||
|
firstName: "Test",
|
||||||
|
lastName: "Testsson",
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { render, type RenderOptions } from "@testing-library/react"
|
|
||||||
import React, { type ReactElement } from "react"
|
|
||||||
|
|
||||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
|
||||||
|
|
||||||
import messages from "@/i18n/dictionaries/en.json"
|
|
||||||
import ClientIntlProvider from "@/i18n/Provider"
|
|
||||||
|
|
||||||
function AllTheProviders({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<ClientIntlProvider
|
|
||||||
defaultLocale={Lang.en}
|
|
||||||
locale={Lang.en}
|
|
||||||
messages={messages}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ClientIntlProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const customRender = (
|
|
||||||
ui: ReactElement,
|
|
||||||
options?: Omit<RenderOptions, "wrapper">
|
|
||||||
) => render(ui, { wrapper: AllTheProviders, ...options })
|
|
||||||
|
|
||||||
export * from "@testing-library/react"
|
|
||||||
export { customRender as render }
|
|
||||||
@@ -3045,7 +3045,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@playwright/test@npm:^1.53.1":
|
"@playwright/test@npm:^1.53.1, @playwright/test@npm:^1.57.0":
|
||||||
version: 1.57.0
|
version: 1.57.0
|
||||||
resolution: "@playwright/test@npm:1.57.0"
|
resolution: "@playwright/test@npm:1.57.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5446,6 +5446,7 @@ __metadata:
|
|||||||
"@netlify/blobs": "npm:^8.1.0"
|
"@netlify/blobs": "npm:^8.1.0"
|
||||||
"@netlify/functions": "npm:^3.0.0"
|
"@netlify/functions": "npm:^3.0.0"
|
||||||
"@netlify/plugin-nextjs": "npm:^5.14.4"
|
"@netlify/plugin-nextjs": "npm:^5.14.4"
|
||||||
|
"@playwright/test": "npm:^1.57.0"
|
||||||
"@radix-ui/react-slot": "npm:^1.2.2"
|
"@radix-ui/react-slot": "npm:^1.2.2"
|
||||||
"@react-aria/ssr": "npm:^3.9.8"
|
"@react-aria/ssr": "npm:^3.9.8"
|
||||||
"@react-aria/test-utils": "npm:1.0.0-alpha.8"
|
"@react-aria/test-utils": "npm:1.0.0-alpha.8"
|
||||||
|
|||||||
Reference in New Issue
Block a user