Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Playwright Text Snapshots
on:
pull_request:
branches:
- master
- main
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: true

jobs:
playwright:
name: playwright aria snapshots
runs-on: ubuntu-latest
timeout-minutes: 20

env:
CI: true
RAILS_ENV: test
PLAYWRIGHT_USER: testuser
PLAYWRIGHT_PASSWORD: testuser

services:
database:
image: mariadb:10.5.4-focal
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: otwarchive_test
ports:
- 3306:3306

elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.3.0
ports:
- 9200:9200
options: >-
-e="discovery.type=single-node"
-e="xpack.security.enabled=false"

memcached:
image: memcached:1.5
ports:
- 11211:11211

steps:
- name: Check out code
uses: actions/checkout@v7

- name: Run apt-get update
run: sudo apt-get update
timeout-minutes: 2

- name: Install and start up redis servers
run: |
sudo apt-get install -y redis-server
./script/gh-actions/multiple_redis.sh
timeout-minutes: 5

- name: Set up Ruby and run bundle install
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Copy config files
run: |
cp config/gh-actions/database.yml config/database.yml
cp config/gh-actions/redis.yml config/redis.yml
cp config/gh-actions/local.yml config/local.yml

- name: Set transaction isolation level
run: ./script/gh-actions/configure_database.sh

- name: Make and seed database
run: bundle exec rake db:otwseed

- name: Prepare elasticsearch indexes and the test user
run: bundle exec rails runner script/playwright_prepare.rb

- name: Start the Rails server
run: |
bundle exec rails server -p 3000 &
for i in $(seq 1 60); do
curl -s -o /dev/null http://localhost:3000/ && break
sleep 2
done
curl -s -o /dev/null -w "server responded with %{http_code}\n" http://localhost:3000/

- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm

- name: Install Playwright
run: |
npm ci
npx playwright install --with-deps chromium
timeout-minutes: 10

- name: Run Playwright tests
run: npm run test:playwright

- name: Upload Playwright report
if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: |
playwright-report
test-results
log/test.log
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,9 @@ coverage
redis-cucumber-dump.rdb

.rspec

# Playwright
node_modules/
playwright/.auth/
playwright-report/
test-results/
58 changes: 58 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "otwarchive",
"private": true,
"scripts": {
"test:playwright": "playwright test",
"test:playwright:update": "playwright test --update-snapshots"
},
"devDependencies": {
"@playwright/test": "1.64.1"
}
}
23 changes: 23 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineConfig, devices } from "@playwright/test";

// See playwright/README.md for how to run these tests and update baselines.
export default defineConfig({
testDir: "./playwright/tests",
fullyParallel: false,
retries: 0,
reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : [["list"]],
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL || "http://localhost:3000",
},
projects: [
{
name: "setup",
testMatch: /auth\.setup\.ts/,
},
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
dependencies: ["setup"],
},
],
});
46 changes: 46 additions & 0 deletions playwright/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Playwright text snapshot checks

These tests capture [aria snapshots](https://playwright.dev/docs/aria-snapshots)
(the page's accessibility tree: roles plus visible text, stored as YAML) of
selected pages and fail with a readable text diff when the rendered text
changes. A green check means the visible text of the covered pages is
unchanged; a red one shows exactly what changed. This is useful for any
refactor that should not change what the user sees, i18n extractions being
the first use case: with the text verified, review can focus on key names
and interpolation.

## Running locally

The tests run against a test server seeded with `db:otwseed`, the same data
CI uses. Snapshots taken against a development database would only match
your machine. Start the server and run the tests:

```sh
docker compose run --rm -p 3001:3000 test bash -c \
"bundle exec rake db:otwseed && \
bundle exec rails runner 'script/playwright_prepare.rb' && \
bundle exec rails server -p 3000 -b 0.0.0.0"
```

```sh
npm install
npx playwright install chromium
PLAYWRIGHT_BASE_URL=http://localhost:3001 PLAYWRIGHT_PASSWORD=testuser \
npm run test:playwright
```

## When a test fails

- If the text change is unintentional, you found a regression.
- If it is intentional, regenerate the baselines and commit the updated
`.aria.yml` files, so the diff documents the change in the pull request.

With the seeded test server from the section above running:

```sh
PLAYWRIGHT_BASE_URL=http://localhost:3001 PLAYWRIGHT_PASSWORD=testuser \
npm run test:playwright:update
```

Review regenerated baselines before committing: auto-generated regexes for
dynamic content (dates, counts) can be too literal and may need widening.
14 changes: 14 additions & 0 deletions playwright/tests/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { test as setup, expect } from "@playwright/test";

const authFile = "playwright/.auth/user.json";
const user = process.env.PLAYWRIGHT_USER || "testuser";
const password = process.env.PLAYWRIGHT_PASSWORD || "password";

setup("authenticate", async ({ page }) => {
await page.goto("/users/login");
await page.locator("#user_login").fill(user);
await page.locator("#user_password").fill(password);
await page.locator("form#new_user").getByRole("button", { name: /log in/i }).click();
await expect(page.locator("#main")).toContainText(/successfully logged in/i);
await page.context().storageState({ path: authFile });
});
13 changes: 13 additions & 0 deletions playwright/tests/new_work_form.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { test, expect } from "@playwright/test";

test.use({ storageState: "playwright/.auth/user.json" });

// The aria snapshot records the visible text of the page (roles and
// accessible names) and fails with a text diff when it changes. If the
// change is intentional, regenerate with: npm run test:playwright:update
test("new work form text is unchanged", async ({ page }) => {
await page.goto("/works/new");
await expect(page.getByRole("main")).toMatchAriaSnapshot({
name: "new_work_form.aria.yml",
});
});
Loading
Loading