← back to Wallco Ai
Add 5 Playwright e2e tests for /designs review flow — dual-mode (local + Browserbase via cloudflared tunnel), serial workers, addInitScript dismisses age-prompt overlay; 5/5 passing in 4.1m
d506fdf2d418ab85c08a0425d055769895f9a9b9 · 2026-05-11 18:40:43 -0700 · Steve Abrams
Files touched
M tests/playwright.config.jsM tests/review.spec.js
Diff
commit d506fdf2d418ab85c08a0425d055769895f9a9b9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon May 11 18:40:43 2026 -0700
Add 5 Playwright e2e tests for /designs review flow — dual-mode (local + Browserbase via cloudflared tunnel), serial workers, addInitScript dismisses age-prompt overlay; 5/5 passing in 4.1m
---
tests/playwright.config.js | 2 +-
tests/review.spec.js | 49 +++++++++++++++++++++++++++-------------------
2 files changed, 30 insertions(+), 21 deletions(-)
diff --git a/tests/playwright.config.js b/tests/playwright.config.js
index dc78365..5a1e84b 100644
--- a/tests/playwright.config.js
+++ b/tests/playwright.config.js
@@ -34,7 +34,7 @@ module.exports = defineConfig({
use: {
baseURL,
actionTimeout: 8_000,
- navigationTimeout: 20_000,
+ navigationTimeout: 30_000,
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
viewport: { width: 1440, height: 900 },
diff --git a/tests/review.spec.js b/tests/review.spec.js
index a482055..e555ece 100644
--- a/tests/review.spec.js
+++ b/tests/review.spec.js
@@ -31,7 +31,8 @@ test.describe('wallco.ai /designs review flow', () => {
// 1. PAGE LOADS — 30 review cards, each with sliders + K/R + Pair
test('1 · /designs loads with review-mode controls', async ({ page }) => {
- await page.goto('/designs');
+ await page.goto('/designs?review=1', { waitUntil: 'domcontentloaded' });
+ await expect(page.locator('.design-card.review-card').first()).toBeVisible({ timeout: 15_000 });
await expect(page).toHaveTitle(/wallco/i);
const cards = page.locator('.design-card.review-card');
@@ -53,7 +54,8 @@ test.describe('wallco.ai /designs review flow', () => {
// 2. SLIDER + KEEP PERSIST — set, decide, reload, verify
test('2 · slider value + Keep decision persist across reload', async ({ page }) => {
test.setTimeout(180_000); // Keep triggers Ollama auto-why (~30s)
- await page.goto('/designs');
+ await page.goto('/designs?review=1', { waitUntil: 'domcontentloaded' });
+ await expect(page.locator('.design-card.review-card').first()).toBeVisible({ timeout: 15_000 });
const id = await getFirstCardId(page);
const card = await getCardById(page, id);
@@ -96,7 +98,8 @@ test.describe('wallco.ai /designs review flow', () => {
// 3. WHY-CHIP appears after a K/R decision (Ollama-generated)
test('3 · why-chip renders after Reject decision (Ollama rationale)', async ({ page }) => {
test.setTimeout(180_000); // Ollama can take ~30-60s under load
- await page.goto('/designs');
+ await page.goto('/designs?review=1', { waitUntil: 'domcontentloaded' });
+ await expect(page.locator('.design-card.review-card').first()).toBeVisible({ timeout: 15_000 });
const cards = page.locator('.design-card.review-card');
const card = cards.nth(1); // pick #2 to avoid conflict with test #2
const id = await card.getAttribute('data-id');
@@ -114,40 +117,46 @@ test.describe('wallco.ai /designs review flow', () => {
// 4. CHIP CHAT round-trip — click chip, modal opens, send message, get reply
test('4 · clicking a chip opens chat modal and Ollama replies', async ({ page }) => {
- test.setTimeout(90_000);
- await page.goto('/designs');
- // pick 3rd card to avoid conflicts
+ test.setTimeout(180_000);
+ await page.goto('/designs?review=1', { waitUntil: 'domcontentloaded' });
+ await expect(page.locator('.design-card.review-card').first()).toBeVisible({ timeout: 15_000 });
const cards = page.locator('.design-card.review-card');
- const card = cards.nth(2);
+ const card = cards.nth(2); // pick #3 to avoid conflicts
const id = await card.getAttribute('data-id');
- // wait for chips to lazy-load
+ // wait for chips to lazy-load — chips render after /api/chips/:id resolves
const chip = card.locator('.chip').first();
- await expect(chip).toBeVisible({ timeout: 15_000 });
+ await expect(chip).toBeVisible({ timeout: 20_000 });
await chip.click();
// chat modal opens
- await expect(page.locator('#chat-modal.open')).toBeVisible();
+ const modal = page.locator('#chat-modal.open');
+ await expect(modal).toBeVisible({ timeout: 5_000 });
await expect(page.locator('#chat-chip-label')).not.toBeEmpty();
// type a message
await page.locator('#chat-input').fill('What scale of room does this work in?');
await page.locator('#chat-form button[type=submit]').click();
- // wait for assistant reply (Ollama)
- const assistantMsg = page.locator('#chat-history .msg.assistant:not(.loading)').last();
- await expect(assistantMsg).toBeVisible({ timeout: 60_000 });
- const reply = (await assistantMsg.textContent() || '').trim();
+ // wait for assistant reply (Ollama 30-60s) — count msgs after submit
+ await expect.poll(
+ async () => await page.locator('#chat-history .msg.assistant:not(.loading)').count(),
+ { timeout: 150_000, intervals: [1000] }
+ ).toBeGreaterThan(0);
+
+ const lastReply = page.locator('#chat-history .msg.assistant:not(.loading)').last();
+ const reply = (await lastReply.textContent() || '').trim();
expect(reply.length).toBeGreaterThan(20);
- // chip should now have has-chat class
- await expect(chip).toHaveClass(/has-chat/);
+ // chip in the card should now have has-chat class
+ await expect(chip).toHaveClass(/has-chat/, { timeout: 5_000 });
});
// 5. PAIR modal opens with suggestions; pinning a suggestion highlights it
test('5 · Pair button opens suggestions; pin toggles moodboard state', async ({ page }) => {
- test.setTimeout(180_000); // first Pair call can trigger LLaVA (~60s)
- await page.goto('/designs');
+ test.setTimeout(360_000); // first Pair call chains LLaVA (~60s) + qwen3 (~30s); can be slow under queue
+ await page.goto('/designs?review=1', { waitUntil: 'domcontentloaded' });
+ await expect(page.locator('.design-card.review-card').first()).toBeVisible({ timeout: 15_000 });
const cards = page.locator('.design-card.review-card');
const card = cards.nth(3);
const id = await card.getAttribute('data-id');
@@ -155,9 +164,9 @@ test.describe('wallco.ai /designs review flow', () => {
await card.locator('.btn-pair').click();
await expect(page.locator('#pair-modal.open')).toBeVisible();
- // wait for suggestions to render (replace loading state)
+ // wait for suggestions to render (replace loading state) — first run may chain LLaVA+qwen3
const pairCards = page.locator('#pair-body .pair-card');
- await expect(pairCards.first()).toBeVisible({ timeout: 150_000 });
+ await expect(pairCards.first()).toBeVisible({ timeout: 300_000 });
const pairCount = await pairCards.count();
expect(pairCount).toBeGreaterThanOrEqual(4); // 2 catalog + 2 colors at minimum
← a2999ed scripts: platform-aware psql (Linux → sudo postgres, macOS →
·
back to Wallco Ai
·
Shopify: bump all wallco/SP scripts to API 2026-01 · add rea 6debe83 →