Merge pull request #576 from QAComet/qacomet/login-tests

Added playwright test setup and login tests
This commit is contained in:
Daniel 2024-04-27 09:01:52 +03:30 committed by GitHub
commit eb8eb74a32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 510 additions and 40 deletions

143
.github/workflows/playwright-tests.yml vendored Normal file
View File

@ -0,0 +1,143 @@
name: Linkwarden Playwright Tests
on:
push:
branches:
- main
- qacomet/**
pull_request:
workflow_dispatch:
env:
PGHOST: localhost
PGPORT: 5432
PGUSER: postgres
PGPASSWORD: password
PGDATABASE: postgres
TEST_POSTGRES_USER: test_linkwarden_user
TEST_POSTGRES_PASSWORD: password
TEST_POSTGRES_DATABASE: test_linkwarden_db
TEST_POSTGRES_DATABASE_TEMPLATE: test_linkwarden_db_template
TEST_POSTGRES_HOST: localhost
TEST_POSTGREST_PORT: 5432
PRODUCTION_POSTGRES_DATABASE: linkwarden_db
NEXTAUTH_SECRET: very_sensitive_secret
NEXTAUTH_URL: http://localhost:3000/api/v1/auth
# Manual installation database settings
DATABASE_URL: postgresql://test_linkwarden_user:password@localhost:5432/test_linkwarden_db
# Docker installation database settings
POSTGRES_PASSWORD: password
TEST_USERNAME: test-user
TEST_PASSWORD: password
jobs:
playwright-test-runner:
strategy:
matrix:
test_case: ['@login']
timeout-minutes: 20
runs-on:
- ubuntu-22.04
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
cache: 'yarn'
- name: Initialize PostgreSQL
run: |
echo "Initializing Databases"
psql -h localhost -U postgres -d postgres -c "CREATE USER ${{ env.TEST_POSTGRES_USER }} WITH PASSWORD '${{ env.TEST_POSTGRES_PASSWORD }}';"
psql -h localhost -U postgres -d postgres -c "CREATE DATABASE ${{ env.TEST_POSTGRES_DATABASE }} OWNER ${{ env.TEST_POSTGRES_USER }};"
- name: Install packages
run: yarn install -y
- name: Cache playwright dependencies
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: |
ffmpeg fonts-freefont-ttf fonts-ipafont-gothic fonts-tlwg-loma-otf
fonts-unifont fonts-wqy-zenhei gstreamer1.0-libav gstreamer1.0-plugins-bad
gstreamer1.0-plugins-base gstreamer1.0-plugins-good libaa1 libass9
libasyncns0 libavc1394-0 libavcodec58 libavdevice58 libavfilter7
libavformat58 libavutil56 libbluray2 libbs2b0 libcaca0 libcdio-cdda2
libcdio-paranoia2 libcdio19 libcdparanoia0 libchromaprint1 libcodec2-1.0
libdc1394-25 libdca0 libdecor-0-0 libdv4 libdvdnav4 libdvdread8 libegl-mesa0
libegl1 libevdev2 libevent-2.1-7 libfaad2 libffi7 libflac8 libflite1
libfluidsynth3 libfreeaptx0 libgles2 libgme0 libgsm1 libgssdp-1.2-0
libgstreamer-gl1.0-0 libgstreamer-plugins-bad1.0-0
libgstreamer-plugins-base1.0-0 libgstreamer-plugins-good1.0-0 libgupnp-1.2-1
libgupnp-igd-1.0-4 libharfbuzz-icu0 libhyphen0 libiec61883-0
libinstpatch-1.0-2 libjack-jackd2-0 libkate1 libldacbt-enc2 liblilv-0-0
libltc11 libmanette-0.2-0 libmfx1 libmjpegutils-2.1-0 libmodplug1
libmp3lame0 libmpcdec6 libmpeg2encpp-2.1-0 libmpg123-0 libmplex2-2.1-0
libmysofa1 libnice10 libnotify4 libopenal-data libopenal1 libopengl0
libopenh264-6 libopenmpt0 libopenni2-0 libopus0 liborc-0.4-0
libpocketsphinx3 libpostproc55 libpulse0 libqrencode4 libraw1394-11
librubberband2 libsamplerate0 libsbc1 libsdl2-2.0-0 libserd-0-0 libshine3
libshout3 libsndfile1 libsndio7.0 libsord-0-0 libsoundtouch1 libsoup-3.0-0
libsoup-3.0-common libsoxr0 libspandsp2 libspeex1 libsphinxbase3
libsratom-0-0 libsrt1.4-gnutls libsrtp2-1 libssh-gcrypt-4 libswresample3
libswscale5 libtag1v5 libtag1v5-vanilla libtheora0 libtwolame0 libudfread0
libv4l-0 libv4lconvert0 libva-drm2 libva-x11-2 libva2 libvdpau1
libvidstab1.1 libvisual-0.4-0 libvo-aacenc0 libvo-amrwbenc0 libvorbisenc2
libvpx7 libwavpack1 libwebrtc-audio-processing1 libwildmidi2 libwoff1
libx264-163 libxcb-shape0 libxv1 libxvidcore4 libzbar0 libzimg2
libzvbi-common libzvbi0 libzxingcore1 ocl-icd-libopencl1 timgm6mb-soundfont
xfonts-cyrillic xfonts-encodings xfonts-scalable xfonts-utils
- name: Cache playwright browsers
id: cache-playwright
uses: actions/cache@v4
with:
path: ~/.cache/
key: ${{ runner.os }}-playwright-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-playwright-
- name: Install playwright
if: steps.cache-playwright.outputs.cache-hit != 'true'
run: yarn playwright install --with-deps
- name: Setup project
run: |
yarn prisma generate
yarn build
yarn prisma migrate deploy
- name: Start linkwarden server and worker
run: yarn start &
- name: Run Tests
run: npx playwright test --grep ${{ matrix.test_case }}
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: test-results
retention-days: 30

6
.gitignore vendored
View File

@ -42,9 +42,15 @@ prisma/dev.db
# tests
/tests
/test-results/
/blob-report/
/playwright-report/
/playwright/.cache/
/playwright/.auth/
# docker
pgdata
certificates
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

View File

@ -1,6 +1,6 @@
node_modules
.next
public
/public
*.lock
*.log

View File

@ -4,6 +4,7 @@ type Props = {
loading?: boolean;
className?: string;
type?: "button" | "submit" | "reset" | undefined;
"data-testid"?: string;
};
export default function AccentSubmitButton({
@ -12,6 +13,7 @@ export default function AccentSubmitButton({
loading,
className,
type,
"data-testid": dataTestId,
}: Props) {
return (
<button
@ -19,6 +21,7 @@ export default function AccentSubmitButton({
className={`border primary-btn-gradient select-none duration-200 bg-black border-[oklch(var(--p))] hover:border-[#0070b5] rounded-lg text-center px-4 py-2 text-white active:scale-95 tracking-wider w-fit flex justify-center items-center gap-2 ${
className || ""
}`}
data-testid={dataTestId}
onClick={() => {
if (loading !== undefined && !loading && onClick) onClick();
}}

View File

@ -32,8 +32,14 @@ export default function Modal({ toggleModal, className, children }: Props) {
<Drawer.Overlay className="fixed inset-0 bg-black/40" />
<ClickAwayHandler onClickOutside={() => setDrawerIsOpen(false)}>
<Drawer.Content className="flex flex-col rounded-t-2xl h-[90%] mt-24 fixed bottom-0 left-0 right-0 z-30">
<div className="p-4 pb-32 bg-base-100 rounded-t-2xl flex-1 border-neutral-content border-t overflow-y-auto">
<div className="mx-auto w-12 h-1.5 flex-shrink-0 rounded-full bg-neutral mb-5" />
<div
className="p-4 pb-32 bg-base-100 rounded-t-2xl flex-1 border-neutral-content border-t overflow-y-auto"
data-testid="mobile-modal-container"
>
<div
className="mx-auto w-12 h-1.5 flex-shrink-0 rounded-full bg-neutral mb-5"
data-testid="mobile-modal-slider"
/>
{children}
</div>
@ -44,19 +50,28 @@ export default function Modal({ toggleModal, className, children }: Props) {
);
} else {
return (
<div className="overflow-y-auto pt-2 sm:py-2 fixed top-0 bottom-0 right-0 left-0 bg-black bg-opacity-10 backdrop-blur-sm flex justify-center items-center fade-in z-40">
<div
className="overflow-y-auto pt-2 sm:py-2 fixed top-0 bottom-0 right-0 left-0 bg-black bg-opacity-10 backdrop-blur-sm flex justify-center items-center fade-in z-40"
data-testid="modal-outer"
>
<ClickAwayHandler
onClickOutside={toggleModal}
className={`w-full mt-auto sm:m-auto sm:w-11/12 sm:max-w-2xl ${
className || ""
}`}
>
<div className="slide-up mt-auto sm:m-auto relative border-neutral-content rounded-t-2xl sm:rounded-2xl border-t sm:border shadow-2xl p-5 bg-base-100 overflow-y-auto sm:overflow-y-visible">
<div
className="slide-up mt-auto sm:m-auto relative border-neutral-content rounded-t-2xl sm:rounded-2xl border-t sm:border shadow-2xl p-5 bg-base-100 overflow-y-auto sm:overflow-y-visible"
data-testid="modal-container"
>
<div
onClick={toggleModal as MouseEventHandler<HTMLDivElement>}
className="absolute top-4 right-3 btn btn-sm outline-none btn-circle btn-ghost z-10"
>
<i className="bi-x text-neutral text-2xl"></i>
<i
className="bi-x text-neutral text-2xl"
data-testid="close-modal-button"
></i>
</div>
{children}
</div>

View File

@ -9,6 +9,7 @@ type Props = {
onKeyDown?: KeyboardEventHandler<HTMLInputElement> | undefined;
className?: string;
spellCheck?: boolean;
"data-testid"?: string;
};
export default function TextInput({
@ -20,9 +21,11 @@ export default function TextInput({
onKeyDown,
className,
spellCheck,
"data-testid": dataTestId,
}: Props) {
return (
<input
data-testid={dataTestId}
spellCheck={spellCheck}
autoFocus={autoFocus}
type={type ? type : "text"}

2
e2e/.env.example Normal file
View File

@ -0,0 +1,2 @@
TEST_USERNAME=test
TEST_PASSWORD=password

20
e2e/data/user.ts Normal file
View File

@ -0,0 +1,20 @@
import axios, { AxiosError } from "axios"
axios.defaults.baseURL = "http://localhost:3000"
export async function seedUser (username?: string, password?: string, name?: string) {
try {
return await axios.post("/api/v1/users", {
username: username || "test",
password: password || "password",
name: name || "Test User",
})
} catch (e: any) {
if (e instanceof AxiosError) {
if (e.response?.status === 400) {
return
}
}
throw e
}
}

View File

@ -0,0 +1,10 @@
import { Locator, Page } from "playwright";
import { BasePage } from "./page";
export class DashboardPage extends BasePage {
container: Locator;
constructor(page: Page) {
super(page);
this.container = this.page.getByTestId("dashboard-wrapper");
}
}

View File

@ -0,0 +1,45 @@
import { Locator, Page } from "@playwright/test";
export class BaseModal {
page: Page;
container: Locator;
mobileContainer: Locator;
closeModalButton: Locator;
mobileModalSlider: Locator;
constructor(page: Page) {
this.page = page;
this.container = page.getByTestId("modal-container");
this.mobileContainer = page.getByTestId("mobile-modal-container");
this.closeModalButton = this.container.getByTestId("close-modal-button");
this.mobileModalSlider = this.mobileContainer.getByTestId(
"mobile-modal-slider"
);
}
async close() {
if (await this.container.isVisible()) {
await this.closeModalButton.click();
}
if (await this.mobileContainer.isVisible()) {
const box = await this.mobileModalSlider.boundingBox();
if (!box) {
return;
}
const pageHeight = await this.page.evaluate(() => window.innerHeight);
const startX = box.x + box.width / 2;
const startY = box.y + box.height / 2;
await this.page.mouse.move(startX, startY);
await this.page.mouse.down();
await this.page.mouse.move(startX, startY + pageHeight / 2);
await this.page.mouse.up();
}
}
async isOpen() {
return (
(await this.container.isVisible()) ||
(await this.mobileContainer.isVisible())
);
}
}

20
e2e/fixtures/base/page.ts Normal file
View File

@ -0,0 +1,20 @@
import { Locator, Page } from "@playwright/test";
export class BasePage {
page: Page;
toastMessage: Locator;
constructor(page: Page) {
this.page = page;
this.toastMessage = this.page.getByTestId("toast-message-container");
}
async getLatestToast() {
const toast = this.toastMessage.first();
return {
locator: toast,
closeButton: toast.getByTestId("close-toast-button"),
message: toast.getByTestId("toast-message"),
};
}
}

27
e2e/fixtures/index.ts Normal file
View File

@ -0,0 +1,27 @@
import { test as baseTest } from "@playwright/test";
import { LoginPage } from "./login-page";
import { RegistrationPage } from "./registration-page";
import { DashboardPage } from "./base/dashboard-page";
export const test = baseTest.extend<{
dashboardPage: DashboardPage;
loginPage: LoginPage;
registrationPage: RegistrationPage;
}>({
page: async ({ page }, use) => {
await page.goto("/");
use(page);
},
dashboardPage: async ({ page }, use) => {
const dashboardPage = new DashboardPage(page);
await use(dashboardPage);
},
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
},
registrationPage: async ({ page }, use) => {
const registrationPage = new RegistrationPage(page);
await use(registrationPage);
},
});

View File

@ -0,0 +1,27 @@
import { Locator, Page } from "@playwright/test";
import { BasePage } from "./base/page";
export class LoginPage extends BasePage {
submitLoginButton: Locator;
loginForm: Locator;
registerLink: Locator;
passwordInput: Locator;
usernameInput: Locator;
constructor(page: Page) {
super(page);
this.submitLoginButton = page.getByTestId("submit-login-button");
this.loginForm = page.getByTestId("login-form");
this.registerLink = page.getByTestId("register-link");
this.passwordInput = page.getByTestId("password-input");
this.usernameInput = page.getByTestId("username-input");
}
async goto() {
await this.page.goto("/login");
}
}

View File

@ -0,0 +1,28 @@
import { Locator, Page } from "@playwright/test";
import { BasePage } from "./base/page";
export class RegistrationPage extends BasePage {
registerButton: Locator;
registrationForm: Locator;
loginLink: Locator;
displayNameInput: Locator;
passwordConfirmInput: Locator;
passwordInput: Locator;
usernameInput: Locator;
constructor(page: Page) {
super(page);
this.registerButton = page.getByTestId("register-button");
this.registrationForm = page.getByTestId("registration-form");
this.loginLink = page.getByTestId("login-link");
this.displayNameInput = page.getByTestId("display-name-input");
this.passwordConfirmInput = page.getByTestId("password-confirm-input");
this.passwordInput = page.getByTestId("password-input");
this.usernameInput = page.getByTestId("username-input");
}
}

2
e2e/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { test } from "./fixtures";
export { expect } from "@playwright/test";

View File

@ -0,0 +1,19 @@
import { seedUser } from "@/e2e/data/user";
import { test as setup } from "../../index";
import { STORAGE_STATE } from "../../../playwright.config";
setup("Setup the default user", async ({ page, dashboardPage, loginPage }) => {
const username = process.env["TEST_USERNAME"] || "";
const password = process.env["TEST_PASSWORD"] || "";
await seedUser(username, password);
await loginPage.goto();
await loginPage.usernameInput.fill(username);
await loginPage.passwordInput.fill(password);
await loginPage.submitLoginButton.click();
await dashboardPage.container.waitFor({ state: "visible" });
await page.context().storageState({
path: STORAGE_STATE,
});
});

View File

@ -0,0 +1,8 @@
import { seedUser } from "@/e2e/data/user";
import { test as setup } from "../../index";
setup("Setup the default user", async () => {
const username = process.env["TEST_USERNAME"] || "";
const password = process.env["TEST_PASSWORD"] || "";
await seedUser(username, password);
});

View File

@ -0,0 +1,50 @@
import { expect, test } from "../../index";
test.describe(
"Login test suite",
{
tag: "@login",
},
async () => {
test("Logging in without credentials displays an error", async ({
loginPage,
}) => {
await loginPage.submitLoginButton.click();
const toast = await loginPage.getLatestToast();
await expect(toast.locator).toBeVisible();
await expect(toast.locator).toHaveAttribute("data-type", "error");
});
test("Logging in with an erroneous password displays an error", async ({
loginPage,
}) => {
await loginPage.usernameInput.fill(process.env["TEST_USERNAME"] || "");
await loginPage.passwordInput.fill("NOT_MY_PASSWORD_DNE_ERROR");
await loginPage.submitLoginButton.click();
const toast = await loginPage.getLatestToast();
await expect(toast.locator).toBeVisible();
await expect(toast.locator).toHaveAttribute("data-type", "error");
});
test("Logging in without valid credentials displays an error", async ({
loginPage,
}) => {
await loginPage.submitLoginButton.click();
const toast = await loginPage.getLatestToast();
await expect(toast.locator).toBeVisible();
await expect(toast.locator).toHaveAttribute("data-type", "error");
});
test("Logging in with a valid username and password works as expected", async ({
page,
loginPage,
dashboardPage,
}) => {
await loginPage.usernameInput.fill(process.env["TEST_USERNAME"] || "");
await loginPage.passwordInput.fill(process.env["TEST_PASSWORD"] || "");
await loginPage.submitLoginButton.click();
await expect(loginPage.loginForm).not.toBeVisible();
await expect(dashboardPage.container).toBeVisible();
});
}
);

View File

@ -6,13 +6,21 @@ import React, { ReactNode, useEffect } from "react";
interface Props {
text?: string;
children: ReactNode;
"data-testid"?: string;
}
export default function CenteredForm({ text, children }: Props) {
export default function CenteredForm({
text,
children,
"data-testid": dataTestId,
}: Props) {
const { settings } = useLocalSettingsStore();
return (
<div className="absolute top-0 bottom-0 left-0 right-0 flex justify-center items-center p-5">
<div
className="absolute top-0 bottom-0 left-0 right-0 flex justify-center items-center p-5"
data-testid={dataTestId}
>
<div className="m-auto flex flex-col gap-2 w-full">
{settings.theme ? (
<Image

View File

@ -38,7 +38,7 @@ export default function MainLayout({ children }: Props) {
<AnnouncementBar toggleAnnouncementBar={toggleAnnouncementBar} />
) : undefined}
<div className="flex">
<div className="flex" data-testid="dashboard-wrapper">
<div className="hidden lg:block">
<Sidebar
className={`fixed ${showAnnouncement ? "top-10" : "top-0"}`}

View File

@ -16,6 +16,7 @@
"start": "concurrently -P \"next start {@}\" \"yarn worker:prod\" --",
"build": "next build",
"lint": "next lint",
"e2e": "playwright test e2e",
"format": "prettier --write \"**/*.{ts,tsx,js,json,md}\""
},
"dependencies": {
@ -54,7 +55,7 @@
"next-auth": "^4.22.1",
"node-fetch": "^2.7.0",
"nodemailer": "^6.9.3",
"playwright": "^1.35.1",
"playwright": "^1.43.1",
"react": "18.2.0",
"react-colorful": "^5.6.1",
"react-dom": "18.2.0",
@ -69,7 +70,7 @@
"zustand": "^4.3.8"
},
"devDependencies": {
"@playwright/test": "^1.35.1",
"@playwright/test": "^1.43.1",
"@types/bcrypt": "^5.0.0",
"@types/dompurify": "^3.0.4",
"@types/jsdom": "^21.1.3",

View File

@ -69,7 +69,7 @@ export default function Login({
function displayLoginCredential() {
if (availableLogins.credentialsEnabled === "true") {
return (
<>
<div data-testid="login-form">
<p className="text-3xl text-black dark:text-white text-center font-extralight">
Enter your credentials
</p>
@ -87,6 +87,7 @@ export default function Login({
placeholder="johnny"
value={form.username}
className="bg-base-100"
data-testid="username-input"
onChange={(e) => setForm({ ...form, username: e.target.value })}
/>
</div>
@ -100,6 +101,7 @@ export default function Login({
placeholder="••••••••••••••"
value={form.password}
className="bg-base-100"
data-testid="password-input"
onChange={(e) => setForm({ ...form, password: e.target.value })}
/>
{availableLogins.emailEnabled === "true" && (
@ -107,6 +109,7 @@ export default function Login({
<Link
href={"/forgot"}
className="text-gray-500 dark:text-gray-400 font-semibold"
data-testid="forgot-password-link"
>
Forgot Password?
</Link>
@ -117,13 +120,14 @@ export default function Login({
type="submit"
label="Login"
className=" w-full text-center"
data-testid="submit-login-button"
loading={submitLoader}
/>
{availableLogins.buttonAuths.length > 0 ? (
<div className="divider my-1">OR</div>
) : undefined}
</>
</div>
);
}
}
@ -155,6 +159,7 @@ export default function Login({
<Link
href={"/register"}
className="block text-black dark:text-white font-semibold"
data-testid="register-link"
>
Sign Up
</Link>

View File

@ -102,6 +102,7 @@ export default function Register() {
} days of Premium Service at no cost!`
: "Create a new account"
}
data-testid="registration-form"
>
{process.env.NEXT_PUBLIC_DISABLE_REGISTRATION === "true" ? (
<div className="p-4 flex flex-col gap-3 justify-between max-w-[30rem] min-w-80 w-full bg-base-200 rounded-2xl shadow-md border border-neutral-content">
@ -127,6 +128,7 @@ export default function Register() {
placeholder="Johnny"
value={form.name}
className="bg-base-100"
data-testid="display-name-input"
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
</div>
@ -139,6 +141,7 @@ export default function Register() {
placeholder="john"
value={form.username}
className="bg-base-100"
data-testid="username-input"
onChange={(e) =>
setForm({ ...form, username: e.target.value })
}
@ -155,6 +158,7 @@ export default function Register() {
placeholder="johnny@example.com"
value={form.email}
className="bg-base-100"
data-testid="email-input"
onChange={(e) => setForm({ ...form, email: e.target.value })}
/>
</div>
@ -168,6 +172,7 @@ export default function Register() {
placeholder="••••••••••••••"
value={form.password}
className="bg-base-100"
data-testid="password-input"
onChange={(e) => setForm({ ...form, password: e.target.value })}
/>
</div>
@ -182,6 +187,7 @@ export default function Register() {
placeholder="••••••••••••••"
value={form.passwordConfirmation}
className="bg-base-100"
data-testid="password-confirm-input"
onChange={(e) =>
setForm({ ...form, passwordConfirmation: e.target.value })
}
@ -195,6 +201,7 @@ export default function Register() {
<Link
href="https://linkwarden.app/tos"
className="font-semibold underline"
data-testid="terms-of-service-link"
>
Terms of Service
</Link>{" "}
@ -202,6 +209,7 @@ export default function Register() {
<Link
href="https://linkwarden.app/privacy-policy"
className="font-semibold underline"
data-testid="privacy-policy-link"
>
Privacy Policy
</Link>
@ -212,6 +220,7 @@ export default function Register() {
<Link
href="mailto:support@linkwarden.app"
className="font-semibold underline"
data-testid="support-link"
>
Get in touch
</Link>
@ -225,10 +234,15 @@ export default function Register() {
label="Sign Up"
className="w-full"
loading={submitLoader}
data-testid="register-button"
/>
<div className="flex items-baseline gap-1 justify-center">
<p className="w-fit text-neutral">Already have an account?</p>
<Link href={"/login"} className="block font-bold">
<Link
href={"/login"}
className="block font-bold"
data-testid="login-link"
>
Login
</Link>
</div>

View File

@ -1,11 +1,8 @@
import { defineConfig, devices } from "@playwright/test";
import path from "path";
import "dotenv/config.js";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();
export const STORAGE_STATE = path.join(__dirname, "playwright/.auth/user.json");
/**
* See https://playwright.dev/docs/test-configuration.
*/
@ -24,7 +21,7 @@ export default defineConfig({
/* 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',
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",
@ -33,10 +30,27 @@ export default defineConfig({
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
name: "setup dashboard",
testMatch: /global\/setup\.dashboard\.ts/,
},
{
name: "setup public",
testMatch: /global\/setup\.public\.ts/,
},
{
name: "chromium dashboard",
dependencies: ["setup dashboard"],
testMatch: "dashboard/*.spec.ts",
use: { ...devices["Desktop Chrome"], storageState: STORAGE_STATE },
},
{
name: "chromium public",
dependencies: ["setup public"],
testMatch: "public/*.spec.ts",
use: { ...devices["Desktop Chrome"] },
},
/*
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
@ -46,6 +60,7 @@ export default defineConfig({
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
*/
/* Test against mobile viewports. */
// {

View File

@ -1279,15 +1279,12 @@
tiny-glob "^0.2.9"
tslib "^2.4.0"
"@playwright/test@^1.35.1":
version "1.35.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.35.1.tgz#a596b61e15b980716696f149cc7a2002f003580c"
integrity sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==
"@playwright/test@^1.43.1":
version "1.43.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.43.1.tgz#16728a59eb8ce0f60472f98d8886d6cab0fa3e42"
integrity sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==
dependencies:
"@types/node" "*"
playwright-core "1.35.1"
optionalDependencies:
fsevents "2.3.2"
playwright "1.43.1"
"@prisma/client@^4.16.2":
version "4.16.2"
@ -4920,17 +4917,19 @@ pixelmatch@^4.0.2:
dependencies:
pngjs "^3.0.0"
playwright-core@1.35.1:
version "1.35.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.1.tgz#52c1e6ffaa6a8c29de1a5bdf8cce0ce290ffb81d"
integrity sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==
playwright-core@1.43.1:
version "1.43.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.43.1.tgz#0eafef9994c69c02a1a3825a4343e56c99c03b02"
integrity sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==
playwright@^1.35.1:
version "1.35.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.35.1.tgz#f991d0c76ae517d4a0023d9428b09d19d5e87128"
integrity sha512-NbwBeGJLu5m7VGM0+xtlmLAH9VUfWwYOhUi/lSEDyGg46r1CA9RWlvoc5yywxR9AzQb0mOCm7bWtOXV7/w43ZA==
playwright@1.43.1, playwright@^1.43.1:
version "1.43.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.43.1.tgz#8ad08984ac66c9ef3d0db035be54dd7ec9f1c7d9"
integrity sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==
dependencies:
playwright-core "1.35.1"
playwright-core "1.43.1"
optionalDependencies:
fsevents "2.3.2"
pngjs@^3.0.0, pngjs@^3.3.3:
version "3.4.0"