E2E tests refactoring (#1318)

* Adds test todos

* Can't seem to change locales

* WIP playwright test refactoring

* jest-playwright cleanup

* Test fixes

* Test fixes

* More test fixes

* WIP: Testing fixes

* More test fixes

* Removes unused files

* Installs missing browsers for e2e

* ts-node fixes

* ts-check fixes

* Type fixes

* Fixes e2e

* FFS

* Renamex webhook snapshot

* Fixes webhook cross-platform

* Renamed webhook snapshot

* Apply suggestions from code review

Co-authored-by: Max Schmitt <max@schmitt.mx>

* Removes kont dependency

* Cleanup playwright options

* Next.js cache optimizations on CI

* Uses cache on e2e as well

* Fixme is introducing side-effects

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <pumfleet@hey.com>
Co-authored-by: Max Schmitt <max@schmitt.mx>
pull/1328/head
Omar López 2021-12-15 09:25:49 -07:00 committed by GitHub
parent 972402be2c
commit e6f71c81bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 521 additions and 847 deletions

View File

@ -40,7 +40,11 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: yarn prisma migrate deploy
- run: yarn test

View File

@ -51,7 +51,11 @@ jobs:
uses: actions/cache@v2
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}-nextjs
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: yarn prisma migrate deploy
- run: yarn db-seed
@ -71,7 +75,7 @@ jobs:
key: cache-playwright-${{ hashFiles('**/yarn.lock') }}
- name: Install playwright deps
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: yarn playwright install-deps
run: yarn playwright install --with-deps
- run: yarn test-playwright

1
.gitignore vendored
View File

@ -14,6 +14,7 @@
.nyc_output
playwright/videos
playwright/screenshots
playwright/artifacts
# next.js
/.next/

View File

@ -201,7 +201,7 @@ export default function Shell(props: {
<Toaster position="bottom-right" />
</div>
<div className="flex h-screen overflow-hidden bg-gray-100">
<div className="flex h-screen overflow-hidden bg-gray-100" data-testid="dashboard-shell">
<div className="hidden md:flex lg:flex-shrink-0">
<div className="flex flex-col w-14 lg:w-56">
<div className="flex flex-col flex-1 h-0 bg-white border-r border-gray-200">

View File

@ -1,35 +0,0 @@
const opts = {
// launch headless on CI, in browser locally
headless: !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS,
collectCoverage: false, // not possible in Next.js 12
executablePath: process.env.PLAYWRIGHT_CHROME_EXECUTABLE_PATH,
locale: "en", // So tests won't fail if local machine is not in english
};
console.log("⚙️ Playwright options:", JSON.stringify(opts, null, 4));
module.exports = {
verbose: true,
preset: "jest-playwright-preset",
transform: {
"^.+\\.ts$": "ts-jest",
},
testMatch: ["<rootDir>/playwright/**/*(*.)@(spec|test).[jt]s?(x)"],
testEnvironmentOptions: {
"jest-playwright": {
browsers: ["chromium" /*, 'firefox', 'webkit'*/],
exitOnPageError: false,
launchType: "LAUNCH",
launchOptions: {
headless: opts.headless,
executablePath: opts.executablePath,
},
contextOptions: {
recordVideo: {
dir: "playwright/videos",
},
},
collectCoverage: opts.collectCoverage,
},
},
};

View File

@ -10,24 +10,23 @@
"db-up": "docker-compose up -d",
"db-migrate": "yarn prisma migrate dev",
"db-deploy": "yarn prisma migrate deploy",
"db-seed": "yarn ts-node scripts/seed.ts",
"db-seed": "ts-node scripts/seed.ts",
"db-nuke": "docker-compose down --volumes --remove-orphans",
"db-setup": "run-s db-up db-migrate db-seed",
"db-reset": "run-s db-nuke db-setup",
"deploy": "run-s build db-deploy",
"dx": "env-cmd run-s db-setup dev",
"test": "jest",
"test-playwright": "jest --config jest.playwright.config.js",
"test-playwright": "playwright test",
"test-codegen": "yarn playwright codegen http://localhost:3000",
"type-check": "tsc --pretty --noEmit",
"build": "next build",
"start": "next start",
"ts-node": "ts-node --compiler-options \"{\\\"module\\\":\\\"commonjs\\\"}\"",
"postinstall": "prisma generate",
"pre-commit": "lint-staged",
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
"prepare": "husky install",
"check-changed-files": "yarn ts-node scripts/ts-check-changed-files.ts"
"check-changed-files": "ts-node scripts/ts-check-changed-files.ts"
},
"engines": {
"node": ">=14.x",
@ -105,6 +104,7 @@
},
"devDependencies": {
"@microsoft/microsoft-graph-types-beta": "0.15.0-preview",
"@playwright/test": "^1.17.1",
"@tailwindcss/forms": "^0.4.0",
"@trivago/prettier-plugin-sort-imports": "2.0.4",
"@types/accept-language-parser": "1.5.2",
@ -134,13 +134,9 @@
"eslint-plugin-react-hooks": "^4.3.0",
"husky": "^7.0.1",
"jest": "^26.0.0",
"jest-playwright": "^0.0.1",
"jest-playwright-preset": "^1.7.0",
"kont": "^0.5.1",
"lint-staged": "^11.1.2",
"mockdate": "^3.0.5",
"npm-run-all": "^4.1.5",
"playwright": "^1.16.2",
"postcss": "^8.4.4",
"prettier": "^2.3.2",
"prisma": "^2.30.2",

View File

@ -24,7 +24,7 @@ import React, { useEffect, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { FormattedNumber, IntlProvider } from "react-intl";
import { useMutation } from "react-query";
import Select, { OptionTypeBase } from "react-select";
import Select from "react-select";
import { StripeData } from "@ee/lib/stripe/server";
@ -59,6 +59,12 @@ import * as RadioArea from "@components/ui/form/radio-area";
dayjs.extend(utc);
dayjs.extend(timezone);
type OptionTypeBase = {
label: string;
value: LocationType;
disabled?: boolean;
};
const addDefaultLocationOptions = (
defaultLocations: OptionTypeBase[],
locationOptions: OptionTypeBase[]
@ -295,8 +301,10 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
classNamePrefix="react-select"
className="flex-1 block w-full min-w-0 border border-gray-300 rounded-sm react-select-container focus:ring-primary-500 focus:border-primary-500 sm:text-sm"
onChange={(e) => {
locationFormMethods.setValue("locationType", e?.value);
openLocationModal(e?.value);
if (e?.value) {
locationFormMethods.setValue("locationType", e.value);
openLocationModal(e.value);
}
}}
/>
</div>
@ -461,7 +469,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
centered
title={t("event_type_title", { eventTypeTitle: eventType.title })}
heading={
<div className="relative group cursor-pointer" onClick={() => setEditIcon(false)}>
<div className="relative cursor-pointer group" onClick={() => setEditIcon(false)}>
{editIcon ? (
<>
<h1
@ -469,7 +477,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
className="inline pl-0 text-gray-900 focus:text-black group-hover:text-gray-500">
{eventType.title}
</h1>
<PencilIcon className="-mt-1 ml-1 inline w-4 h-4 text-gray-700 group-hover:text-gray-500" />
<PencilIcon className="inline w-4 h-4 ml-1 -mt-1 text-gray-700 group-hover:text-gray-500" />
</>
) : (
<div style={{ marginBottom: -11 }}>
@ -478,7 +486,7 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
autoFocus
style={{ top: -6, fontSize: 22 }}
required
className="w-full relative pl-0 h-10 text-gray-900 bg-transparent border-none cursor-pointer focus:text-black hover:text-gray-700 focus:ring-0 focus:outline-none"
className="relative w-full h-10 pl-0 text-gray-900 bg-transparent border-none cursor-pointer focus:text-black hover:text-gray-700 focus:ring-0 focus:outline-none"
placeholder={t("quick_chat")}
{...formMethods.register("title")}
defaultValue={eventType.title}
@ -639,6 +647,9 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
value={asStringOrUndefined(eventType.schedulingType)}
options={schedulingTypeOptions}
onChange={(val) => {
// FIXME
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
formMethods.setValue("schedulingType", val);
}}
/>
@ -1154,8 +1165,10 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
classNamePrefix="react-select"
className="flex-1 block w-full min-w-0 my-4 border border-gray-300 rounded-sm react-select-container focus:ring-primary-500 focus:border-primary-500 sm:text-sm"
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
setSelectedLocation(val);
}
}}
/>
)}

View File

@ -417,7 +417,7 @@ export default function Onboarding(props: inferSSRProps<typeof getServerSideProp
}
return (
<div className="min-h-screen bg-brand">
<div className="min-h-screen bg-brand" data-testid="onboarding">
<Head>
<title>Cal.com - {t("getting_started")}</title>
<link rel="icon" href="/favicon.ico" />

36
playwright.config.ts Normal file
View File

@ -0,0 +1,36 @@
import { PlaywrightTestConfig, devices } from "@playwright/test";
const config: PlaywrightTestConfig = {
forbidOnly: !!process.env.CI,
testDir: "playwright",
timeout: 60_000,
retries: process.env.CI ? 3 : 0,
globalSetup: require.resolve("./playwright/lib/globalSetup"),
use: {
baseURL: "http://localhost:3000",
locale: "en-US",
trace: "on-first-retry",
headless: !!process.env.CI || !!process.env.PLAYWRIGHT_HEADLESS,
contextOptions: {
recordVideo: {
dir: "playwright/videos",
},
},
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
/* {
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
}, */
],
};
export default config;

View File

@ -1,42 +1,32 @@
import { kont } from "kont";
import { test, expect } from "@playwright/test";
import { pageProvider } from "./lib/pageProvider";
import { todo } from "./lib/testUtils";
jest.setTimeout(60e3);
if (process.env.CI) {
jest.retryTimes(3);
}
describe("free user", () => {
const ctx = kont()
.useBeforeEach(pageProvider({ path: "/free" }))
.done();
test("only one visible event", async () => {
const { page } = ctx;
await expect(page).toHaveSelector(`[href="/free/30min"]`);
await expect(page).not.toHaveSelector(`[href="/free/60min"]`);
test.describe("free user", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/free");
});
test("only one visible event", async ({ page }) => {
await expect(page.locator(`[href="/free/30min"]`)).toBeVisible();
await expect(page.locator(`[href="/free/60min"]`)).not.toBeVisible();
});
test.todo("`/free/30min` is bookable");
todo("`/free/30min` is bookable");
test.todo("`/free/60min` is not bookable");
todo("`/free/60min` is not bookable");
});
describe("pro user", () => {
const ctx = kont()
.useBeforeEach(pageProvider({ path: "/pro" }))
.done();
test.describe("pro user", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/pro");
});
test("pro user's page has at least 2 visible events", async () => {
const { page } = ctx;
test("pro user's page has at least 2 visible events", async ({ page }) => {
const $eventTypes = await page.$$("[data-testid=event-types] > *");
expect($eventTypes.length).toBeGreaterThanOrEqual(2);
});
test("book an event first day in next month", async () => {
const { page } = ctx;
test("book an event first day in next month", async ({ page }) => {
// Click first event type
await page.click('[data-testid="event-type-link"]');
// Click [data-testid="incrementMonth"]
@ -58,7 +48,7 @@ describe("pro user", () => {
});
});
test.todo("Can reschedule the recently created booking");
todo("Can reschedule the recently created booking");
test.todo("Can cancel the recently created booking");
todo("Can cancel the recently created booking");
});

View File

@ -1,24 +1,17 @@
import { kont } from "kont";
import { expect, test } from "@playwright/test";
import { loginProvider } from "./lib/loginProvider";
import { randomString } from "./lib/testUtils";
jest.setTimeout(60e3);
jest.retryTimes(3);
test.beforeEach(async ({ page }) => {
await page.goto("/event-types");
// We wait until loading is finished
await page.waitForSelector('[data-testid="event-types"]');
});
describe("pro user", () => {
const ctx = kont()
.useBeforeEach(
loginProvider({
user: "pro",
path: "/event-types",
waitForSelector: "[data-testid=event-types]",
})
)
.done();
test.describe("pro user", () => {
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
test("has at least 2 events", async () => {
const { page } = ctx;
test("has at least 2 events", async ({ page }) => {
const $eventTypes = await page.$$("[data-testid=event-types] > *");
expect($eventTypes.length).toBeGreaterThanOrEqual(2);
@ -27,8 +20,7 @@ describe("pro user", () => {
}
});
test("can add new event type", async () => {
const { page } = ctx;
test("can add new event type", async ({ page }) => {
await page.click("[data-testid=new-event-type]");
const nonce = randomString(3);
const eventTitle = `hello ${nonce}`;
@ -43,25 +35,16 @@ describe("pro user", () => {
},
});
await page.goto("http://localhost:3000/event-types");
await page.goto("/event-types");
await expect(page).toHaveSelector(`text='${eventTitle}'`);
expect(page.locator(`text='${eventTitle}'`)).toBeTruthy();
});
});
describe("free user", () => {
const ctx = kont()
.useBeforeEach(
loginProvider({
user: "free",
path: "/event-types",
waitForSelector: "[data-testid=event-types]",
})
)
.done();
test.describe("free user", () => {
test.use({ storageState: "playwright/artifacts/freeStorageState.json" });
test("has at least 2 events where first is enabled", async () => {
const { page } = ctx;
test("has at least 2 events where first is enabled", async ({ page }) => {
const $eventTypes = await page.$$("[data-testid=event-types] > *");
expect($eventTypes.length).toBeGreaterThanOrEqual(2);
@ -71,9 +54,7 @@ describe("free user", () => {
expect(await $last.getAttribute("data-disabled")).toBe("1");
});
test("can not add new event type", async () => {
const { page } = ctx;
await expect(page.$("[data-testid=new-event-type]")).toBeDisabled();
test("can not add new event type", async ({ page }) => {
await expect(page.locator("[data-testid=new-event-type]")).toBeDisabled();
});
});

View File

@ -1,41 +1,44 @@
import { kont } from "kont";
import { expect, test } from "@playwright/test";
import { loginProvider } from "./lib/loginProvider";
import { createHttpServer, waitFor } from "./lib/testUtils";
import { createHttpServer, todo, waitFor } from "./lib/testUtils";
jest.setTimeout(60e3);
jest.retryTimes(3);
test.describe("integrations", () => {
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
describe("webhooks", () => {
const ctx = kont()
.useBeforeEach(
loginProvider({
user: "pro",
path: "/integrations",
waitForSelector: '[data-testid="new_webhook"]',
})
)
.done();
test.beforeEach(async ({ page }) => {
await page.goto("/integrations");
});
test("add webhook & test that creating an event triggers a webhook call", async () => {
const { page } = ctx;
todo("Can add Zoom integration");
todo("Can add Stripe integration");
todo("Can add Google Calendar");
todo("Can add Office 365 Calendar");
todo("Can add CalDav Calendar");
todo("Can add Apple Calendar");
test("add webhook & test that creating an event triggers a webhook call", async ({ page }, testInfo) => {
const webhookReceiver = createHttpServer();
// --- add webhook
await page.click('[data-testid="new_webhook"]');
await expect(page).toHaveSelector("[data-testid='WebhookDialogForm']");
expect(page.locator(`[data-testid='WebhookDialogForm']`)).toBeVisible();
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
await page.click("[type=submit]");
// dialog is closed
await expect(page).not.toHaveSelector("[data-testid='WebhookDialogForm']");
expect(page.locator(`[data-testid='WebhookDialogForm']`)).not.toBeVisible();
// page contains the url
await expect(page).toHaveSelector(`text='${webhookReceiver.url}'`);
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
// --- Book the first available day next month in the pro user's "30min"-event
await page.goto(`http://localhost:3000/pro/30min`);
await page.goto(`/pro/30min`);
await page.click('[data-testid="incrementMonth"]');
await page.click('[data-testid="day"][data-disabled="false"]');
await page.click('[data-testid="time"]');
@ -67,35 +70,9 @@ describe("webhooks", () => {
// if we change the shape of our webhooks, we can simply update this by clicking `u`
// console.log("BODY", body);
expect(body).toMatchInlineSnapshot(`
Object {
"createdAt": "[redacted/dynamic]",
"payload": Object {
"additionInformation": "[redacted/dynamic]",
"attendees": Array [
Object {
"email": "test@example.com",
"name": "Test Testson",
"timeZone": "[redacted/dynamic]",
},
],
"description": "",
"destinationCalendar": null,
"endTime": "[redacted/dynamic]",
"metadata": Object {},
"organizer": Object {
"email": "pro@example.com",
"name": "Pro Example",
"timeZone": "[redacted/dynamic]",
},
"startTime": "[redacted/dynamic]",
"title": "30min between Pro Example and Test Testson",
"type": "30min",
"uid": "[redacted/dynamic]",
},
"triggerEvent": "BOOKING_CREATED",
}
`);
// Text files shouldn't have platform specific suffixes
testInfo.snapshotSuffix = "";
expect(JSON.stringify(body)).toMatchSnapshot(`webhookResponse.txt`);
webhookReceiver.close();
});

View File

@ -0,0 +1 @@
{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30min","title":"30min between Pro Example and Test Testson","description":"","startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"Pro Example","email":"pro@example.com","timeZone":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]"}],"destinationCalendar":null,"uid":"[redacted/dynamic]","metadata":{},"additionInformation":"[redacted/dynamic]"}}

View File

@ -0,0 +1,37 @@
import { Browser, chromium } from "@playwright/test";
async function loginAsUser(username: string, browser: Browser) {
const page = await browser.newPage();
await page.goto("http://localhost:3000/auth/login");
// Click input[name="email"]
await page.click('input[name="email"]');
// Fill input[name="email"]
await page.fill('input[name="email"]', `${username}@example.com`);
// Press Tab
await page.press('input[name="email"]', "Tab");
// Fill input[name="password"]
await page.fill('input[name="password"]', username);
// Press Enter
await page.press('input[name="password"]', "Enter");
await page.waitForSelector(
username === "onboarding" ? "[data-testid=onboarding]" : "[data-testid=dashboard-shell]"
);
// Save signed-in state to '${username}StorageState.json'.
await page.context().storageState({ path: `playwright/artifacts/${username}StorageState.json` });
await page.context().close();
}
async function globalSetup(/* config: FullConfig */) {
const browser = await chromium.launch();
await loginAsUser("onboarding", browser);
// await loginAsUser("free-first-hidden", browser);
await loginAsUser("pro", browser);
// await loginAsUser("trial", browser);
await loginAsUser("free", browser);
// await loginAsUser("usa", browser);
// await loginAsUser("teamfree", browser);
// await loginAsUser("teampro", browser);
await browser.close();
}
export default globalSetup;

View File

@ -1,87 +0,0 @@
/* eslint-disable @typescript-eslint/ban-types */
import { provider, Provider } from "kont";
import { Page, Cookie } from "playwright";
/**
* Context data that Login provder needs.
*/
export type Needs = {};
/**
* Login provider's options.
*/
export type Params = {
user: string;
};
/**
* Context data that Page provider contributes.
*/
export type Contributes = {
page: Page;
};
const cookieCache = new Map<string, Cookie[]>();
/**
* Creates a new context / "incognito tab" and logs in the specified user
*/
export function loginProvider(opts: {
user: string;
/**
* Path to navigate to after login
*/
path?: string;
/**
* Selector to wait for to decide that the navigation is done
*/
waitForSelector?: string;
}): Provider<Needs, Contributes> {
return provider<Needs, Contributes>()
.name("login")
.before(async () => {
const context = await browser.newContext();
const page = await context.newPage();
const cachedCookies = cookieCache.get(opts.user);
if (cachedCookies) {
await context.addCookies(cachedCookies);
} else {
await page.goto("http://localhost:3000/auth/login");
// Click input[name="email"]
await page.click('input[name="email"]');
// Fill input[name="email"]
await page.fill('input[name="email"]', `${opts.user}@example.com`);
// Press Tab
await page.press('input[name="email"]', "Tab");
// Fill input[name="password"]
await page.fill('input[name="password"]', opts.user);
// Press Enter
await page.press('input[name="password"]', "Enter");
await page.waitForNavigation({
url(url) {
return !url.pathname.startsWith("/auth");
},
});
const cookies = await context.cookies();
cookieCache.set(opts.user, cookies);
}
if (opts.path) {
await page.goto(`http://localhost:3000${opts.path}`);
}
if (opts.waitForSelector) {
await page.waitForSelector(opts.waitForSelector);
}
return {
page,
context,
};
})
.after(async (ctx) => {
await ctx.page?.close();
await ctx.context?.close();
})
.done();
}

View File

@ -1,51 +0,0 @@
/* eslint-disable @typescript-eslint/ban-types */
import { provider, Provider } from "kont";
import { Page } from "playwright";
/**
* Context data that Page provder needs.
*/
export type Needs = {};
/**
* Page provider's options.
*/
export type Params = {
user: string;
};
/**
* Context data that Page provider contributes.
*/
export type Contributes = {
page: Page;
};
/**
* Creates a new context / "incognito tab" and logs in the specified user
*/
export function pageProvider(opts: {
/**
* Path to navigate to
*/
path: string;
}): Provider<Needs, Contributes> {
return provider<Needs, Contributes>()
.name("page")
.before(async () => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(`http://localhost:3000${opts.path}`);
return {
page,
context,
};
})
.after(async (ctx) => {
await ctx.page?.close();
await ctx.context?.close();
})
.done();
}

View File

@ -1,5 +1,11 @@
import { test } from "@playwright/test";
import { createServer, IncomingMessage, ServerResponse } from "http";
export function todo(title: string) {
// eslint-disable-next-line @typescript-eslint/no-empty-function
test.skip(title, () => {});
}
export function randomString(length = 12) {
let result = "";
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

View File

@ -1,23 +1,11 @@
jest.setTimeout(60e3);
import { test } from "@playwright/test";
test("login with pro@example.com", async () => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("http://localhost:3000/auth/login");
// Click input[name="email"]
await page.click('input[name="email"]');
// Fill input[name="email"]
await page.fill('input[name="email"]', `pro@example.com`);
// Press Tab
await page.press('input[name="email"]', "Tab");
// Fill input[name="password"]
await page.fill('input[name="password"]', `pro`);
// Press Enter
await page.press('input[name="password"]', "Enter");
// Using logged in state from globalSteup
test.use({ storageState: "playwright/artifacts/proStorageState.json" });
test("login with pro@example.com", async ({ page }) => {
// Try to go homepage
await page.goto("/");
// It should redirect you to the event-types page
await page.waitForSelector("[data-testid=event-types]");
await context.close();
});
export {};

View File

@ -1,22 +1,14 @@
import { kont } from "kont";
import { test } from "@playwright/test";
import { loginProvider } from "./lib/loginProvider";
test.describe("Onboarding", () => {
test.use({ storageState: "playwright/artifacts/onboardingStorageState.json" });
jest.setTimeout(60e3);
jest.retryTimes(2);
const ctx = kont()
.useBeforeEach(
loginProvider({
user: "onboarding",
})
)
.done();
test("redirects to /getting-started after login", async () => {
await ctx.page.waitForNavigation({
test("redirects to /getting-started after login", async ({ page }) => {
await page.goto("/event-types");
await page.waitForNavigation({
url(url) {
return url.pathname === "/getting-started";
},
});
});
});

View File

@ -35,8 +35,6 @@
"jsx": "preserve",
"types": [
"@types/jest",
"jest-playwright-preset",
"expect-playwright"
],
"allowJs": true,
"incremental": true
@ -49,5 +47,11 @@
],
"exclude": [
"node_modules"
]
],
"ts-node": {
"compilerOptions": {
"module": "CommonJS",
"types": ["node"],
}
}
}

813
yarn.lock

File diff suppressed because it is too large Load Diff