carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

1// Every user action, performed the way a person would with JavaScript on
2// (double-click, checkbox clicks) or off (the contract's submit controls
3// and edit link). Each one returns once the server has answered.
4import { expect, type Page, type Locator, type TestInfo } from '@playwright/test';
5
6export const jsOn = (ti: TestInfo) => ti.project.name === 'js-on';
7export const todos = (page: Page) => page.locator('.todo-list li');
8
9// Runs fn and waits for what it causes: a page load with JS off, the POST
10// response with JS on.
11async function settle(page: Page, ti: TestInfo, fn: () => Promise<void>) {
12 if (jsOn(ti)) {
13 await Promise.all([page.waitForResponse((r) => r.request().method() === 'POST'), fn()]);
14 } else {
15 await Promise.all([page.waitForEvent('load'), fn()]);
16 }
17}
18
19export async function addTodo(page: Page, ti: TestInfo, title: string) {
20 const input = page.locator('.new-todo');
21 await input.fill(title);
22 await settle(page, ti, () => input.press('Enter'));
23}
24
25export async function addDefaultTodos(page: Page, ti: TestInfo, titles: string[]) {
26 for (const t of titles) await addTodo(page, ti, t);
27 await expect(todos(page)).toHaveCount(titles.length);
28}
29
30export async function toggle(page: Page, ti: TestInfo, item: Locator) {
31 const control = jsOn(ti) ? item.locator('.toggle') : item.locator('[data-action="toggle"]');
32 await settle(page, ti, () => control.click());
33}
34
35export async function toggleAll(page: Page, ti: TestInfo) {
36 const control = jsOn(ti)
37 ? page.locator('label[for="toggle-all"]')
38 : page.locator('[data-action="toggle-all"]');
39 await settle(page, ti, () => control.click());
40}
41
42export async function destroy(page: Page, ti: TestInfo, item: Locator) {
43 await item.hover();
44 await settle(page, ti, () => item.locator('.destroy').click());
45}
46
47export async function clearCompleted(page: Page, ti: TestInfo) {
48 await settle(page, ti, () => page.locator('.clear-completed').click());
49}
50
51// Opens the item's editor: double-click with JS, the edit link without.
52// Returns the edit input.
53export async function startEditing(page: Page, ti: TestInfo, index: number): Promise<Locator> {
54 const item = todos(page).nth(index);
55 if (jsOn(ti)) {
56 await item.locator('label').dblclick();
57 } else {
58 await Promise.all([page.waitForEvent('load'), item.locator('a[data-action="edit"]').click()]);
59 }
60 const input = todos(page).nth(index).locator('.edit');
61 await expect(input).toBeVisible();
62 return input;
63}
64
65export async function submitEdit(page: Page, ti: TestInfo, input: Locator, title: string) {
66 await input.fill(title);
67 await settle(page, ti, () => input.press('Enter'));
68}
69
70export async function gotoFilter(page: Page, name: 'All' | 'Active' | 'Completed') {
71 const path = { All: '/', Active: '/active', Completed: '/completed' }[name];
72 await page.locator('.filters a', { hasText: name }).click();
73 await expect(page).toHaveURL((u) => u.pathname === path);
74}
75