Skip to content

Commit 861592a

Browse files
committed
Port over our playwright utilities for client-side nav
1 parent 2d47d8c commit 861592a

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

site/e2e/tests/login.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { test } from "@playwright/test"
22
import { ProjectsPage, SignInPage } from "../pom"
33
import { email, password } from "../constants"
4+
import { waitForClientSideNavigation } from "./../util"
45

56
test("Login takes user to /projects", async ({ baseURL, page }) => {
67
await page.goto(baseURL + "/", { waitUntil: "networkidle" })
@@ -10,7 +11,7 @@ test("Login takes user to /projects", async ({ baseURL, page }) => {
1011
await signInPage.submitBuiltInAuthentication(email, password)
1112

1213
const projectsPage = new ProjectsPage(baseURL, page)
13-
await page.waitForNavigation({ url: projectsPage.url, waitUntil: "networkidle" })
14+
await waitForClientSideNavigation(page, { to: projectsPage.url })
1415

1516
await page.waitForSelector("text=Projects")
1617
})

site/e2e/util.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Page } from "@playwright/test"
2+
3+
/**
4+
* `timeout(x)` is a helper function to create a promise that resolves after `x` milliseconds.
5+
*
6+
* @param timeoutInMilliseconds Time to wait for promise to resolve
7+
* @returns `Promise`
8+
*/
9+
export const timeout = (timeoutInMilliseconds: number): Promise<void> => {
10+
return new Promise((resolve) => {
11+
setTimeout(resolve, timeoutInMilliseconds)
12+
})
13+
}
14+
15+
/**
16+
* `waitFor(f, timeout?)` waits for a predicate to return `true`, running it periodically until it returns `true`.
17+
*
18+
* If `f` never returns `true`, the function will simply return. In other words, the burden is on the consumer
19+
* to check that the predicate is passing (`waitFor` does no validation).
20+
*
21+
* @param f A predicate that returns a `Promise<boolean>`
22+
* @param timeToWaitInMilliseconds The total time to wait for the condition to be `true`.
23+
* @returns
24+
*/
25+
export const waitFor = async (f: () => Promise<boolean>, timeToWaitInMilliseconds = 30000): Promise<void> => {
26+
let elapsedTime = 0
27+
const timeToWaitPerIteration = 1000
28+
29+
while (elapsedTime < timeToWaitInMilliseconds) {
30+
const condition = await f()
31+
32+
if (condition) {
33+
return
34+
}
35+
36+
await timeout(timeToWaitPerIteration)
37+
elapsedTime += timeToWaitPerIteration
38+
}
39+
}
40+
41+
interface WaitForClientSideNavigationOpts {
42+
/**
43+
* from is the page before navigation (the 'current' page)
44+
*/
45+
from?: string
46+
/**
47+
* to is the page after navigation (the 'next' page)
48+
*/
49+
to?: string
50+
}
51+
52+
/**
53+
* waitForClientSideNavigation waits for the url to change from opts.from to
54+
* opts.to (if specified), as well as a network idle load state. This enhances
55+
* a native playwright check for navigation or loadstate.
56+
*
57+
* @remark This is necessary in a client-side SPA world since playwright
58+
* waitForNavigation waits for load events on the DOM (ex: after a page load
59+
* from the server).
60+
*
61+
* @todo Better logging for this.
62+
*/
63+
export const waitForClientSideNavigation = async (page: Page, opts: WaitForClientSideNavigationOpts): Promise<void> => {
64+
await Promise.all([
65+
waitFor(() => {
66+
const conditions: boolean[] = []
67+
68+
if (opts.from) {
69+
conditions.push(page.url() !== opts.from)
70+
}
71+
72+
if (opts.to) {
73+
conditions.push(page.url() === opts.to)
74+
}
75+
76+
const unmetConditions = conditions.filter((condition) => !condition)
77+
78+
return Promise.resolve(unmetConditions.length === 0)
79+
}),
80+
page.waitForLoadState("networkidle"),
81+
])
82+
}

0 commit comments

Comments
 (0)