-
Notifications
You must be signed in to change notification settings - Fork 174
Add pre-deploy test gate and fix Pages two-publisher race #887
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| name: Website Testing | ||
|
|
||
| # Runs on PRs and pushes to validate the website, and is called from | ||
| # release.yml as the pre-deploy gate. | ||
| # | ||
| # All events run: npm ci -> unit tests -> build. | ||
| # Release dispatch / manual dispatch additionally run Playwright e2e. | ||
| # PR / push runs skip the heavier browser suite to keep feedback fast. | ||
| on: | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - 'website/**' | ||
| - '.github/workflows/website-test.yml' | ||
| push: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - 'website/**' | ||
| - '.github/workflows/website-test.yml' | ||
| workflow_call: | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| test-website: | ||
| name: Test website | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
|
|
||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| ref: ${{ github.sha }} | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | ||
| with: | ||
| node-version: 24 | ||
| cache: npm | ||
| cache-dependency-path: website/package-lock.json | ||
|
|
||
| - name: Install dependencies | ||
| working-directory: website | ||
| run: npm ci | ||
|
|
||
| - name: Run unit tests | ||
| working-directory: website | ||
| run: npm test | ||
|
|
||
| - name: Build website | ||
| working-directory: website | ||
| run: npm run build | ||
|
|
||
| # ----- e2e steps below: only on release dispatch and manual runs ----- | ||
|
|
||
| - name: Install Playwright browsers | ||
| if: ${{ github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' }} | ||
| working-directory: website | ||
| run: npx playwright install chromium --with-deps | ||
|
|
||
| - name: Run Playwright tests | ||
| if: ${{ github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch' }} | ||
| working-directory: website | ||
| run: npx playwright test | ||
|
|
||
| - name: Upload Playwright report | ||
| if: ${{ failure() && (github.event_name == 'workflow_call' || github.event_name == 'workflow_dispatch') }} | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: playwright-report | ||
| path: website/playwright-report/ | ||
| retention-days: 7 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| import { test, expect } from "@playwright/test"; | ||
|
|
||
| // End-to-end coverage modeled on Azure/ai-app-templates' website.spec.ts, | ||
| // adapted for awesome-azd's paginated gallery (20 cards/page). Assertions | ||
| // against the filtered template *total* read the live "Viewing N templates" | ||
| // status text, since the rendered `.fui-Card` count is capped by pagination. | ||
| test.describe("Gallery functionality (deploy gate)", () => { | ||
| test.beforeEach(async ({ page }) => { | ||
| await page.goto("./"); | ||
| await page.waitForLoadState("networkidle"); | ||
| await page.waitForSelector(".fui-Card", { timeout: 15_000 }); | ||
| }); | ||
|
|
||
| // Reads the total from the "Viewing N templates" live region. The page | ||
| // has multiple aria-live polite regions (color-mode toggle, copy toast, | ||
| // status counter) so we filter by the literal "Viewing" text. | ||
| // | ||
| // The status string takes three forms depending on totalItems: | ||
| // "Viewing 0 templates" (no results) | ||
| // "Viewing 1 template" (single result) | ||
| // "Viewing 1-20 of 290 templates" (paginated, > 1 result) | ||
| // In every case the LAST integer is the total. | ||
| async function viewingCount(page: import("@playwright/test").Page): Promise<number> { | ||
| const status = page | ||
| .locator('[role="status"]') | ||
| .filter({ hasText: /Viewing/ }) | ||
| .first(); | ||
| await expect(status).toBeVisible(); | ||
| const text = (await status.textContent()) ?? ""; | ||
| const nums = text.match(/\d+/g); | ||
| expect(nums, `expected numbers in status: "${text}"`).not.toBeNull(); | ||
| return Number(nums![nums!.length - 1]); | ||
| } | ||
|
|
||
| test("homepage renders without 404 / error fallback", async ({ page }) => { | ||
| const body = (await page.textContent("body")) ?? ""; | ||
| expect(body).not.toContain("Page Not Found"); | ||
| expect(body).not.toContain("404 Not Found"); | ||
| await expect(page.locator("body")).toBeVisible(); | ||
| }); | ||
|
|
||
| test("gallery shows a healthy number of templates", async ({ page }) => { | ||
| // Deploy-gate guardrail: if the data pipeline regresses or the gallery | ||
| // fails to hydrate, the live counter drops to ~0. A real release has 100+. | ||
| const total = await viewingCount(page); | ||
| console.log(`Initial gallery total: ${total}`); | ||
| expect(total).toBeGreaterThanOrEqual(50); | ||
| }); | ||
|
|
||
| test("page renders at least one template card", async ({ page }) => { | ||
| const cards = page.locator(".fui-Card"); | ||
| const rendered = await cards.count(); | ||
| expect(rendered).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| test('search "azure" narrows results but keeps some', async ({ page }) => { | ||
| const initial = await viewingCount(page); | ||
| expect(initial).toBeGreaterThan(0); | ||
|
|
||
| const searchInput = page.locator("#filterBar"); | ||
| await searchInput.click(); | ||
| await searchInput.fill("azure"); | ||
| // The SearchBox's onSearch (URL update + filtering) only fires on Enter. | ||
| await searchInput.press("Enter"); | ||
|
|
||
| await expect(page).toHaveURL(/name=azure/); | ||
| await expect | ||
| .poll(async () => viewingCount(page), { timeout: 10_000 }) | ||
| .not.toBe(initial); | ||
|
|
||
| const filtered = await viewingCount(page); | ||
| console.log(`Gallery total after searching "azure": ${filtered}`); | ||
| expect(filtered).toBeGreaterThan(0); | ||
| expect(filtered).toBeLessThanOrEqual(initial); | ||
| }); | ||
|
|
||
| test("nonsense search shows the empty-state message", async ({ page }) => { | ||
| const searchInput = page.locator("#filterBar"); | ||
| await searchInput.click(); | ||
| await searchInput.fill("zzzznonexistenttemplate"); | ||
| await searchInput.press("Enter"); | ||
|
|
||
| await expect(page.locator("text=No templates found")).toBeVisible({ | ||
| timeout: 10_000, | ||
| }); | ||
| }); | ||
|
|
||
| test("section filter (Language → Python) narrows the gallery", async ({ | ||
| page, | ||
| }) => { | ||
| const initial = await viewingCount(page); | ||
| expect(initial).toBeGreaterThan(0); | ||
|
|
||
| // The left-side filter panel uses checkboxes; clicking one applies a | ||
| // tag filter and updates the URL with ?tags=... | ||
| // Use the deterministic Fluent UI Checkbox id from ShowcaseLeftFilters | ||
| // (`showcase_checkbox_id_<tag>`) to avoid matching "Python" mentions | ||
| // inside template card titles, descriptions, or tag chips. | ||
| // ShowcaseLeftFilters has two levels of accordions: an outer one per | ||
| // section (Language, Framework, etc.) and an inner "View All" that | ||
| // hides tags after the first 6. Python is in the Language section, so | ||
| // expand that section first, then expand "View All" if needed. | ||
| const pythonCheckbox = page.locator("#showcase_checkbox_id_python"); | ||
| if (!(await pythonCheckbox.isVisible().catch(() => false))) { | ||
| await page.getByRole("button", { name: /^Language/ }).first().click(); | ||
| } | ||
| if (!(await pythonCheckbox.isVisible().catch(() => false))) { | ||
| const viewAllButtons = page.getByRole("button", { name: /view all/i }); | ||
| const count = await viewAllButtons.count(); | ||
| for (let i = 0; i < count; i++) { | ||
| await viewAllButtons.nth(i).click(); | ||
| } | ||
| } | ||
| await expect(pythonCheckbox).toBeVisible({ timeout: 10_000 }); | ||
| await pythonCheckbox.click(); | ||
|
|
||
| await expect(page).toHaveURL(/tags=/); | ||
| await expect | ||
| .poll(async () => viewingCount(page), { timeout: 10_000 }) | ||
| .not.toBe(initial); | ||
|
|
||
| const filtered = await viewingCount(page); | ||
| console.log(`Gallery total after filtering by Python: ${filtered}`); | ||
| expect(filtered).toBeGreaterThan(0); | ||
| expect(filtered).toBeLessThan(initial); | ||
| }); | ||
|
|
||
| test("clearing search restores the full gallery", async ({ page }) => { | ||
| const initial = await viewingCount(page); | ||
|
|
||
| const searchInput = page.locator("#filterBar"); | ||
| await searchInput.fill("python"); | ||
| await searchInput.press("Enter"); | ||
| await expect(page).toHaveURL(/name=python/); | ||
| await expect | ||
| .poll(async () => viewingCount(page), { timeout: 10_000 }) | ||
| .not.toBe(initial); | ||
|
|
||
| await searchInput.fill(""); | ||
| await searchInput.press("Enter"); | ||
| await expect(page).not.toHaveURL(/name=python/); | ||
| await expect | ||
| .poll(async () => viewingCount(page), { timeout: 10_000 }) | ||
| .toBe(initial); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.