Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,10 @@ jobs:
name: playwright-report
path: playwright-report/
retention-days: 7

- name: Upload documentation screenshots
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: docs-screenshots
path: docs-screenshots/
retention-days: 30
73 changes: 73 additions & 0 deletions .github/workflows/sync-docs-screenshots.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Sync Documentation Screenshots

on:
pull_request:
types: [closed]

permissions:
contents: read

jobs:
sync:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Find CI run for merged PR
id: find-run
run: |
SHA="${{ github.event.pull_request.head.sha }}"
RUN_ID=$(gh api "repos/${{ github.repository }}/actions/runs?head_sha=$SHA&status=success" \
--jq '.workflow_runs[] | select(.name == "CI") | .id' | head -n1)
if [ -z "$RUN_ID" ]; then
echo "No successful CI run found for $SHA"
exit 1
fi
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ github.token }}

- name: Download screenshots artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: docs-screenshots
path: docs-screenshots
run-id: ${{ steps.find-run.outputs.run_id }}
github-token: ${{ github.token }}

- name: Checkout static site
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
repository: ianpaschal/combat-command-static
token: ${{ secrets.WORKFLOW_AUTOMATION_TOKEN }}
path: static-site

- name: Copy screenshots
run: |
mkdir -p static-site/src/assets/docs/guides
cp -r docs-screenshots/. static-site/src/assets/docs/guides/

- name: Open PR with updated screenshots
working-directory: static-site
run: |
BRANCH="ci/sync-docs-screenshots-${{ github.event.pull_request.number }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add src/assets/docs/guides/
if git diff --staged --quiet; then
echo "No screenshot changes, skipping PR"
exit 0
fi
git commit -m "Docs: Update Screenshots (ianpaschal/combat-command#${{ github.event.pull_request.number }})"
git push -f origin "$BRANCH"
gh pr create \
--title "Docs: Update Screenshots (ianpaschal/combat-command#${{ github.event.pull_request.number }})" \
--body "Automated screenshot update triggered by ianpaschal/combat-command#${{ github.event.pull_request.number }}." \
--head "$BRANCH" \
--base main \
--reviewer ianpaschal \
|| echo "PR may already exist for this branch"
env:
GH_TOKEN: ${{ secrets.WORKFLOW_AUTOMATION_TOKEN }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ lerna-debug.log*
coverage
playwright-report
test-results
docs-screenshots
test/.auth
node_modules
dist
Expand Down
2 changes: 2 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export default defineConfig({
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry',
viewport: { width: 1920, height: 1080 },
},
webServer: [

Expand All @@ -61,6 +62,7 @@ export default defineConfig({
use: {
...devices['Desktop Firefox'],
launchOptions: { slowMo: process.env.SLOW_MO ? parseInt(process.env.SLOW_MO) : 0 },
viewport: { width: 1280, height: 960 },
},
},
],
Expand Down
1 change: 1 addition & 0 deletions src/components/ToastProvider/ToastProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const ToastProvider = ({
{toast && (
<Toast.Root
className={styles.Root}
data-testid="toast"
open={!!toast}
onOpenChange={handleOpenChange}
duration={severityDurations[toast?.severity]}
Expand Down
19 changes: 19 additions & 0 deletions test/helpers/screenshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { type Page } from '@playwright/test';
import fs from 'fs';
import path from 'path';

const screenshotsRoot = path.join(process.cwd(), 'docs-screenshots');

export const screenshot = async (page: Page, name: string) => {
await page.waitForFunction(() => !document.getAnimations().some((a) => {
if (a.playState !== 'running') {
return false;
}
const timing = (a.effect as KeyframeEffect | null)?.getTiming();
return Number.isFinite(timing?.iterations);
}));
Comment thread
ianpaschal marked this conversation as resolved.

const filePath = path.join(screenshotsRoot, `${name}.png`);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
await page.screenshot({ path: filePath });
};
14 changes: 14 additions & 0 deletions test/tournament-pairings.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type Locator } from '@playwright/test';

import { dragTo } from './helpers/dragTo';
import { screenshot } from './helpers/screenshot';
import { signIn } from './helpers/signIn';
import { snapshotPairings } from './helpers/snapshotPairings';
import { api } from '../convex/_generated/api';
Expand Down Expand Up @@ -32,6 +33,9 @@ test.describe('Tournament Pairing Creation', () => {
const dialog = page.getByRole('dialog', { name: 'Auto-Generate Pairings' });
await expect(dialog).toBeVisible();

await expect(dialog.getByRole('checkbox', { name: 'Select all' })).toBeVisible();
await screenshot(page, 'creating-pairings/auto-generate-dialog');

// Select all competitors, then deselect 2 so 22 are included → 11 pairings needed:
await dialog.getByRole('checkbox', { name: 'Select all' }).check();
await dialog.getByRole('checkbox', { name: 'Alex Carter' }).uncheck();
Expand All @@ -53,12 +57,15 @@ test.describe('Tournament Pairing Creation', () => {
const pairingRows = page.getByTestId('pairing-item');
await expect(pairingRows).toHaveCount(11);
await expect(pairingRows.getByLabel('Table Assignment')).toContainText(Array(11).fill('Auto'));
await page.waitForSelector('[data-testid="toast"]', { state: 'hidden' });
await screenshot(page, 'creating-pairings/pairing-list-auto');

// Confirm pairings with table assignments:
await page.getByRole('button', { name: 'Proceed' }).click();
const confirmDialog = page.getByRole('dialog', { name: 'Confirm Pairings' });
await expect(confirmDialog).toBeVisible();
await expect(confirmDialog.getByText('The following pairings will be created:')).toBeVisible();
await screenshot(page, 'creating-pairings/confirm-pairings-dialog');

// Create pairings and return to the tournament detail page:
await confirmDialog.getByRole('button', { name: 'Create' }).click();
Expand Down Expand Up @@ -92,6 +99,7 @@ test.describe('Tournament Pairing Creation', () => {
await page.getByRole('button', { name: 'Configure' }).click();
const dialog = page.getByRole('dialog', { name: 'Configure Pairing Rules' });
await expect(dialog).toBeVisible();
await screenshot(page, 'creating-pairings/configure-pairing-rules-dialog');

// Adjust table count to 3:
await dialog.getByLabel('Table Count').fill('3');
Expand All @@ -104,6 +112,7 @@ test.describe('Tournament Pairing Creation', () => {
await addButton.click();
await addButton.click();
await expect(rows).toHaveCount(3);
await screenshot(page, 'creating-pairings/empty-pairing-slots');

// Create the first pairing by dragging 2 competitors into the slots:
await dragTo(
Expand All @@ -129,6 +138,7 @@ test.describe('Tournament Pairing Creation', () => {
rows.nth(1).locator('[data-over]').nth(0),
);
await expect(rows.nth(1).getByText('Bye')).toBeVisible();
await screenshot(page, 'creating-pairings/pairing-row-bye');

// Remove the unused third row:
await rows.nth(2).getByRole('button', { name: 'Remove Pairing' }).click();
Expand Down Expand Up @@ -204,6 +214,7 @@ test.describe('Tournament Pairing Creation', () => {
const confirmReplaceDialog = page.getByRole('dialog', { name: 'Are you sure you want to auto-generate?' });
await expect(confirmReplaceDialog).toBeVisible();
await expect(confirmReplaceDialog.getByText('Your current pairings will be replaced.')).toBeVisible();
await screenshot(page, 'creating-pairings/replace-confirmation-dialog');

// Cancel, and expect pairings to remain unchanged, and the generate dialog still open:
await confirmReplaceDialog.getByRole('button', { name: 'Cancel' }).click();
Expand Down Expand Up @@ -300,6 +311,7 @@ test.describe('Tournament Pairing Creation', () => {

// Observe repeat match up error:
await expect(rows.nth(0).locator('[data-color="red"]')).toBeVisible();
await screenshot(page, 'creating-pairings/repeat-opponent-warning');

// Replace idB with next unpaired competitor by dragging it onto side B:
await dragTo(
Expand All @@ -315,6 +327,8 @@ test.describe('Tournament Pairing Creation', () => {

// Observe repeat table warning:
await expect(rows.nth(0).locator('[data-color="yellow"]')).toBeVisible();
await page.getByRole('listbox').waitFor({ state: 'hidden' });
await screenshot(page, 'creating-pairings/repeat-table-warning');

// Create another pairing:
await addButton.click();
Expand Down
Loading