Skip to content

Commit

Permalink
Functional Tests: Replace chai with Playwright Assertions
Browse files Browse the repository at this point in the history
Utilize Playwright's built-in [assertions][] so that functional test
assertions are consistent. The `chai`-based assertions provide similar
coverage, but not much is gained for the suite by having two competing
styles of assertion.

The majority of this diff replaces `assert.equal` with
`expect(...).toEqual()`. In some cases, text comparison is replaced with
Playwright's [toHaveText][], since that assertion bakes in
synchronization and waiting support, and has the ability to compare
contents in a way that ignores spacing differences.

There are other patterns throughout the test suite that could be
improved by adhering more closely to Playwright idioms, but that would
can be saved for another time.

Related
---

A reduction in functional test suite execution time was an unexpected
benefit. Since the suite relies less on manual timing interventions like
`nextBody` and `nextBeat`, it relies more on Playwright's built-in
synchronization and timing mechanisms.

```sh
yarn test:browser --project=chrome

 # ...

  352 passed (38s)
✨  Done in 38.17s.
```

```sh
yarn test:browser --project=chrome

 # ...

  349 passed (25s)
✨  Done in 24.89s.
```

[assertions]: https://playwright.dev/docs/test-assertions
[toHaveText]: https://playwright.dev/docs/api/class-locatorassertions#locator-assertions-to-have-text
  • Loading branch information
seanpdoyle committed Nov 17, 2023
1 parent cc263b4 commit 5e71a69
Show file tree
Hide file tree
Showing 24 changed files with 1,056 additions and 1,357 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"@web/test-runner-playwright": "^0.9.0",
"arg": "^5.0.1",
"body-parser": "^1.20.1",
"chai": "~4.3.4",
"eslint": "^8.13.0",
"express": "^4.18.2",
"multer": "^1.4.2",
Expand Down
7 changes: 3 additions & 4 deletions src/tests/functional/async_script_tests.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { test } from "@playwright/test"
import { assert } from "chai"
import { expect, test } from "@playwright/test"
import { readEventLogs, visitAction } from "../helpers/page"

test.beforeEach(async ({ page }) => {
Expand All @@ -10,11 +9,11 @@ test.beforeEach(async ({ page }) => {
test("does not emit turbo:load when loaded asynchronously after DOMContentLoaded", async ({ page }) => {
const events = await readEventLogs(page)

assert.deepEqual(events, [])
expect(events).toEqual([])
})

test("following a link when loaded asynchronously after DOMContentLoaded", async ({ page }) => {
await page.click("#async-link")

assert.equal(await visitAction(page), "advance")
expect(await visitAction(page)).toEqual("advance")
})
126 changes: 30 additions & 96 deletions src/tests/functional/autofocus_tests.js
Original file line number Diff line number Diff line change
@@ -1,150 +1,88 @@
import { test } from "@playwright/test"
import { assert } from "chai"
import { hasSelector, nextBeat } from "../helpers/page"
import { expect, test } from "@playwright/test"

test.beforeEach(async ({ page }) => {
await page.goto("/src/tests/fixtures/autofocus.html")
})

test("autofocus first autofocus element on load", async ({ page }) => {
await nextBeat()
assert.ok(
await hasSelector(page, "#first-autofocus-element:focus"),
await expect(
page.locator("#first-autofocus-element"),
"focuses the first [autofocus] element on the page"
)
assert.notOk(
await hasSelector(page, "#second-autofocus-element:focus"),
).toBeFocused()
await expect(
page.locator("#second-autofocus-element"),
"focuses the first [autofocus] element on the page"
)
).not.toBeFocused()
})

test("autofocus first [autofocus] element on visit", async ({ page }) => {
await page.goto("/src/tests/fixtures/navigation.html")
await page.click("#autofocus-link")
await nextBeat()

assert.ok(
await hasSelector(page, "#first-autofocus-element:focus"),
"focuses the first [autofocus] element on the page"
)
assert.notOk(
await hasSelector(page, "#second-autofocus-element:focus"),
"focuses the first [autofocus] element on the page"
)
await expect(page.locator("#first-autofocus-element")).toBeFocused()
await expect(page.locator("#second-autofocus-element")).not.toBeFocused()
})

test("navigating a frame with a descendant link autofocuses [autofocus]:first-of-type", async ({ page }) => {
await page.click("#frame-inner-link")
await nextBeat()

assert.ok(
await hasSelector(page, "#frames-form-first-autofocus-element:focus"),
"focuses the first [autofocus] element in frame"
)
assert.notOk(
await hasSelector(page, "#frames-form-second-autofocus-element:focus"),
"focuses the first [autofocus] element in frame"
)

await expect(page.locator("#frames-form-first-autofocus-element")).toBeFocused()
await expect(page.locator("#frames-form-second-autofocus-element")).not.toBeFocused()
})

test("autofocus visible [autofocus] element on visit with inert elements", async ({ page }) => {
await page.click("#autofocus-inert-link")
await nextBeat()

assert.notOk(
await hasSelector(page, "#dialog-autofocus-element:focus"),
"autofocus element is ignored in a closed dialog"
)
assert.notOk(
await hasSelector(page, "#details-autofocus-element:focus"),
"autofocus element is ignored in a closed details"
)
assert.notOk(
await hasSelector(page, "#hidden-autofocus-element:focus"),
"autofocus element is ignored in a hidden div"
)
assert.notOk(
await hasSelector(page, "#inert-autofocus-element:focus"),
"autofocus element is ignored in an inert div"
)
assert.notOk(
await hasSelector(page, "#disabled-autofocus-element:focus"),
"autofocus element is ignored when disabled"
)
assert.ok(
await hasSelector(page, "#visible-autofocus-element:focus"),
"focuses the visible [autofocus] element on the page"
)

await expect(page.locator("#dialog-autofocus-element")).not.toBeFocused()
await expect(page.locator("#details-autofocus-element")).not.toBeFocused()
await expect(page.locator("#hidden-autofocus-element")).not.toBeFocused()
await expect(page.locator("#inert-autofocus-element")).not.toBeFocused()
await expect(page.locator("#disabled-autofocus-element")).not.toBeFocused()
await expect(page.locator("#visible-autofocus-element")).toBeFocused()
})

test("navigating a frame with a link targeting the frame autofocuses [autofocus]:first-of-type", async ({
page
}) => {
await page.click("#frame-outer-link")
await nextBeat()

assert.ok(
await hasSelector(page, "#frames-form-first-autofocus-element:focus"),
"focuses the first [autofocus] element in frame"
)
assert.notOk(
await hasSelector(page, "#frames-form-second-autofocus-element:focus"),
"focuses the first [autofocus] element in frame"
)

await expect(page.locator("#frames-form-first-autofocus-element")).toBeFocused()
await expect(page.locator("#frames-form-second-autofocus-element")).not.toBeFocused()
})

test("navigating a frame with a turbo-frame targeting the frame autofocuses [autofocus]:first-of-type", async ({
page
}) => {
await page.click("#drives-frame-target-link")
await nextBeat()

assert.ok(
await hasSelector(page, "#frames-form-first-autofocus-element:focus"),
"focuses the first [autofocus] element in frame"
)
assert.notOk(
await hasSelector(page, "#frames-form-second-autofocus-element:focus"),
"focuses the first [autofocus] element in frame"
)

await expect(page.locator("#frames-form-first-autofocus-element")).toBeFocused()
await expect(page.locator("#frames-form-second-autofocus-element")).not.toBeFocused()
})

test("receiving a Turbo Stream message with an [autofocus] element when the activeElement is the document", async ({ page }) => {
await page.evaluate(() => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur()
}
document.activeElement.blur()
window.Turbo.renderStreamMessage(`
<turbo-stream action="append" targets="body">
<template><input id="autofocus-from-stream" autofocus></template>
</turbo-stream>
`)
})
await nextBeat()

assert.ok(
await hasSelector(page, "#autofocus-from-stream:focus"),
"focuses the [autofocus] element in from the turbo-stream"
)
await expect(page.locator("#autofocus-from-stream")).toBeFocused()
})

test("autofocus from a Turbo Stream message does not leak a placeholder [id]", async ({ page }) => {
await page.evaluate(() => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur()
}
document.activeElement.blur()
window.Turbo.renderStreamMessage(`
<turbo-stream action="append" targets="body">
<template><div id="container-from-stream"><input autofocus></div></template>
</turbo-stream>
`)
})
await nextBeat()

assert.ok(
await hasSelector(page, "#container-from-stream input:focus"),
"focuses the [autofocus] element in from the turbo-stream"
)
await expect(page.locator("#container-from-stream input")).toBeFocused()
})

test("receiving a Turbo Stream message with an [autofocus] element when an element within the document has focus", async ({ page }) => {
Expand All @@ -155,10 +93,6 @@ test("receiving a Turbo Stream message with an [autofocus] element when an eleme
</turbo-stream>
`)
})
await nextBeat()

assert.ok(
await hasSelector(page, "#first-autofocus-element:focus"),
"focuses the first [autofocus] element on the page"
)
await expect(page.locator("#first-autofocus-element")).toBeFocused()
})
22 changes: 9 additions & 13 deletions src/tests/functional/cache_observer_tests.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
import { test } from "@playwright/test"
import { assert } from "chai"
import { hasSelector, nextBody } from "../helpers/page"
import { expect, test } from "@playwright/test"
import { nextEventNamed } from "../helpers/page"

test("removes temporary elements", async ({ page }) => {
await page.goto("/src/tests/fixtures/cache_observer.html")

assert.equal(await page.textContent("#temporary"), "data-turbo-temporary")
await expect(page.locator("#temporary")).toHaveText("data-turbo-temporary")

await page.click("#link")
await nextBody(page)
await nextEventNamed(page, "turbo:load")
await page.goBack()
await nextBody(page)

assert.notOk(await hasSelector(page, "#temporary"))
await expect(page.locator("#temporary")).not.toBeVisible()
})

test("removes temporary elements with deprecated turbo-cache=false selector", async ({ page }) => {
await page.goto("/src/tests/fixtures/cache_observer.html")

assert.equal(await page.textContent("#temporary-with-deprecated-selector"), "data-turbo-cache=false")
await expect(page.locator("#temporary-with-deprecated-selector")).toHaveText("data-turbo-cache=false")

await page.click("#link")
await nextBody(page)
await nextEventNamed(page, "turbo:load")
await page.goBack()
await nextBody(page)

assert.notOk(await hasSelector(page, "#temporary-with-deprecated-selector"))
await expect(page.locator("#temporary-with-deprecated-selector")).not.toBeVisible()
})

test("following a redirect renders [data-turbo-temporary] elements before the cache removes", async ({ page }) => {
await page.goto("/src/tests/fixtures/navigation.html")
await page.click("#redirect-to-cache-observer")
await nextBody(page)

assert.equal(await page.textContent("#temporary"), "data-turbo-temporary")
await expect(page.locator("#temporary")).toHaveText("data-turbo-temporary")
})
28 changes: 14 additions & 14 deletions src/tests/functional/drive_disabled_tests.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { test } from "@playwright/test"
import { assert } from "chai"
import { expect, test } from "@playwright/test"
import {
getFromLocalStorage,
nextBody,
nextBeat,
nextEventNamed,
nextEventOnTarget,
pathname,
searchParams,
Expand All @@ -18,30 +18,30 @@ test.beforeEach(async ({ page }) => {

test("drive disabled by default; click normal link", async ({ page }) => {
await page.click("#drive_disabled")
await nextBody(page)
await nextEventNamed(page, "turbo:load")

assert.equal(pathname(page.url()), path)
assert.equal(await visitAction(page), "load")
expect(pathname(page.url())).toEqual(path)
expect(await visitAction(page)).toEqual("load")
})

test("drive disabled by default; click link inside data-turbo='true'", async ({ page }) => {
await page.click("#drive_enabled")
await nextBody(page)
await nextEventNamed(page, "turbo:load")

assert.equal(pathname(page.url()), path)
assert.equal(await visitAction(page), "advance")
expect(pathname(page.url())).toEqual(path)
expect(await visitAction(page)).toEqual("advance")
})

test("drive disabled by default; submit form inside data-turbo='true'", async ({ page }) => {
await setLocalStorageFromEvent(page, "turbo:submit-start", "formSubmitted", "true")

await page.click("#no_submitter_drive_enabled a#requestSubmit")
await nextBody(page)
await nextBeat()

assert.ok(await getFromLocalStorage(page, "formSubmitted"))
assert.equal(pathname(page.url()), "/src/tests/fixtures/form.html")
assert.equal(await visitAction(page), "advance")
assert.equal(await searchParams(page.url()).get("greeting"), "Hello from a redirect")
expect(await getFromLocalStorage(page, "formSubmitted")).toEqual("true")
expect(pathname(page.url())).toEqual("/src/tests/fixtures/form.html")
expect(await visitAction(page)).toEqual("advance")
expect(await searchParams(page.url()).get("greeting")).toEqual("Hello from a redirect")
})

test("drive disabled by default; links within <turbo-frame> navigate with Turbo", async ({ page }) => {
Expand Down
19 changes: 8 additions & 11 deletions src/tests/functional/drive_tests.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { test } from "@playwright/test"
import { assert } from "chai"
import { nextBody, pathname, visitAction } from "../helpers/page"
import { expect, test } from "@playwright/test"
import { nextEventNamed, pathname, visitAction } from "../helpers/page"

const path = "/src/tests/fixtures/drive.html"

Expand All @@ -10,8 +9,8 @@ test.beforeEach(async ({ page }) => {

test("drive enabled by default; click normal link", async ({ page }) => {
await page.click("#drive_enabled")
await nextBody(page)
assert.equal(pathname(page.url()), path)
await nextEventNamed(page, "turbo:load")
expect(pathname(page.url())).toEqual(path)
})

test("drive to external link", async ({ page }) => {
Expand All @@ -20,16 +19,14 @@ test("drive to external link", async ({ page }) => {
})

await page.click("#drive_enabled_external")
await nextBody(page)

assert.equal(await page.evaluate(() => window.location.href), "https://example.com/")
assert.equal(await page.textContent("body"), "Hello from the outside world")
await expect(page).toHaveURL("https://example.com/")
await expect(page.locator("body")).toHaveText("Hello from the outside world")
})

test("drive enabled by default; click link inside data-turbo='false'", async ({ page }) => {
await page.click("#drive_disabled")
await nextBody(page)

assert.equal(pathname(page.url()), path)
assert.equal(await visitAction(page), "load")
await expect(page).toHaveURL(path)
expect(await visitAction(page)).toEqual("load")
})
Loading

0 comments on commit 5e71a69

Please sign in to comment.