← back to CelebritySignatures
verification/gallery-shopping.e2e.cjs
137 lines
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
let pw;
for (const location of ['playwright', path.join(os.homedir(), 'Projects/Designer-Wallcoverings/node_modules/playwright'), path.join(os.homedir(), 'node_modules/playwright')]) {
try { pw = require(location); break; } catch {}
}
if (!pw) throw new Error('Playwright is required');
const base = process.env.WEAR_PROOF_URL || 'http://127.0.0.1:9956';
const artifacts = fs.mkdtempSync(path.join(os.tmpdir(), 'tk10286-shopping-'));
const checks = [];
(async () => {
const browser = await pw.chromium.launch({ channel: 'chrome' });
try {
for (const [name, viewport] of [['desktop', { width:1280, height:900 }], ['mobile', { width:390, height:844 }]]) {
const context = await browser.newContext({ viewport, recordVideo:{ dir:artifacts } });
// Every journey below is navigation or browser-local state only.
await context.route('**/*', route => {
if (!['GET','HEAD'].includes(route.request().method())) return route.abort();
return route.continue();
});
const page = await context.newPage();
const errors = [];
page.on('pageerror', e => errors.push(e.message));
// All three initial catalog responses can redraw cards independently.
const galleryReady = Promise.all(['/api/signatures', '/api/signature-evolution', '/api/portraits'].map(endpoint =>
page.waitForResponse(r => new URL(r.url()).pathname === endpoint).then(r => r.finished())));
await page.goto(base+'/?cat=Politics', { waitUntil:'domcontentloaded' });
await galleryReady;
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
const card = page.locator('.card[data-qid="Q91"]');
await card.waitFor();
assert.equal(await page.locator('.gnav a[href="/wear"]').count(), 1);
assert.ok(await card.locator('.shop-wallpaper').isVisible());
assert.ok(await card.locator('.shop-clothing').isVisible());
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
await card.scrollIntoViewIfNeeded();
await card.locator('.sig img').evaluate(img => img.complete && img.naturalWidth ? true : new Promise((resolve, reject) => { img.onload=resolve; img.onerror=()=>reject(new Error('Signature image unavailable')); }));
await page.screenshot({ path:path.join(artifacts, `${name}-card.png`) });
await card.locator('.shop-clothing').click();
await page.waitForURL('**/wear?**');
await page.locator('#scrim.on').waitFor();
assert.equal(await page.locator('#pName').textContent(), 'Abraham Lincoln');
assert.equal(new URL(page.url()).searchParams.get('qid'), 'Q91');
assert.deepEqual(await page.locator('#garments .chip').allTextContents(), ['Classic Tee', 'Polo', 'Pocket Tee', 'Dad Cap', 'Trucker Cap', 'Bucket Hat', 'Crew Socks', 'Signature Camp Shirt', 'Signature Long-Sleeve']);
await page.locator('#garments [data-g="polo"]').click();
await page.locator('#colors [data-c="black"]').click();
await page.locator('#sizes [data-z="2XL"]').click();
assert.match(await page.locator('#price').textContent(), /48\.00/);
await page.waitForFunction(() => photoCache[state.color.photo]?.naturalWidth > 0 && sigImg?.naturalWidth > 0);
await page.screenshot({ path:path.join(artifacts, `${name}-clothing.png`) });
// The four accessories are now for sale: One size, real price, purchasable, no shirt-only 3D toggle.
const accessoryPrice = { 'dad-cap':'$29.00', 'trucker-cap':'$33.00', 'bucket-hat':'$38.00', 'crew-socks':'$28.00' };
for (const id of ['dad-cap', 'trucker-cap', 'bucket-hat', 'crew-socks']) {
await page.locator(`#garments [data-g="${id}"]`).click();
await page.waitForFunction(id => state.garment.id === id && photoCache[state.color.photo]?.naturalWidth > 0, id);
assert.equal(await page.locator('#sizes .chip').textContent(), 'One size');
assert.equal(await page.locator('#buy').isDisabled(), false);
assert.notEqual(await page.locator('#buy').textContent(), 'Coming soon');
assert.equal(await page.locator('#price').textContent(), accessoryPrice[id]);
assert.equal(await page.locator('#view3dToggle').isVisible(), false);
assert.match(await page.locator('#productNote').textContent(), /front of the (cap|hat)|both socks/);
if (id === 'crew-socks') assert.match(await page.locator('#sizeNote').textContent(), /6–10/);
await page.locator('.preview').screenshot({ path:path.join(artifacts, `${name}-${id}-preview.png`) });
await page.locator('#buy').scrollIntoViewIfNeeded();
await page.screenshot({ path:path.join(artifacts, `${name}-${id}-options.png`) });
}
// The all-over shirts remain coming-soon holds: multi-size, no price, placement-preview note.
for (const id of ['aop-camp-shirt', 'aop-longsleeve-shirt']) {
await page.locator(`#garments [data-g="${id}"]`).click();
await page.waitForFunction(id => state.garment.id === id && photoCache[state.color.photo]?.naturalWidth > 0, id);
assert.ok(await page.locator('#sizes .chip').count() > 1);
assert.equal(await page.locator('#buy').isDisabled(), true);
assert.equal(await page.locator('#buy').textContent(), 'Coming soon');
assert.equal(await page.locator('#price').textContent(), 'Coming soon');
assert.equal(await page.locator('#view3dToggle').isVisible(), false);
assert.match(await page.locator('#productNote').textContent(), /coming soon/i);
await page.locator('.preview').screenshot({ path:path.join(artifacts, `${name}-${id}-preview.png`) });
await page.locator('#buy').scrollIntoViewIfNeeded();
await page.screenshot({ path:path.join(artifacts, `${name}-${id}-options.png`) });
}
await page.locator('#garments [data-g="classic-tee"]').click();
assert.equal(await page.locator('#buy').isDisabled(), false);
// Lincoln opens in the editorial "worn" Signature Edit shot; the 3D toggle is a garment-view control.
assert.ok(await page.locator('#lifestyleShot').isVisible());
assert.equal(await page.locator('#view3dToggle').isVisible(), false);
await page.locator('#lifestyleToggle').click();
assert.equal(await page.locator('#lifestyleShot').isVisible(), false);
assert.equal(await page.locator('#view3dToggle').isVisible(), true);
await page.goBack({ waitUntil:'domcontentloaded' });
assert.equal(new URL(page.url()).searchParams.get('cat'), 'Politics');
await card.locator('.sig').click();
await page.locator('#artistPop:not([hidden]) .ap-shop').waitFor();
const choices = page.locator('.ap-shop');
const box = await choices.boundingBox();
assert.ok(box && box.y >= 0 && box.y + box.height <= viewport.height, 'product choices visible without scrolling the details');
assert.equal(await choices.locator('a').count(), 2);
await page.screenshot({ path:path.join(artifacts, `${name}-details.png`) });
await choices.locator('.shop-wallpaper').click();
await page.waitForURL('**/wallpaper?**');
assert.equal(new URL(page.url()).searchParams.get('qid'), 'Q91');
await page.waitForFunction(() => document.querySelector('#selectedName').textContent === 'Abraham Lincoln');
assert.equal(await page.locator('#previewName').textContent(), 'Abraham Lincoln');
await page.waitForFunction(() => document.querySelector('#wallpaperPreview').dataset.signature === 'Q91');
await page.locator('#previewFrame').scrollIntoViewIfNeeded();
await page.screenshot({ path:path.join(artifacts, `${name}-wallpaper.png`) });
await page.goto(base+'/wear?qid=QUnknownFixture&name=Not+available', { waitUntil:'domcontentloaded' });
await page.locator('#selectionNotice:not([hidden])').waitFor();
assert.match(await page.locator('#selectionNotice').textContent(), /not available on clothing/);
assert.equal(await page.locator('#scrim.on').count(), 0);
assert.ok(await page.locator('#grid .card').count() > 0);
await page.goto(base+'/wear', { waitUntil:'domcontentloaded' });
await page.locator('#grid .card').first().waitFor();
const total = await page.locator('#grid .card').count();
await page.locator('#sigSearch').fill('Lincoln');
await page.locator('#ac .ac-item[data-qid="Q91"]').waitFor();
assert.ok(await page.locator('#grid .card').count() < total);
await page.locator('#sigSearch').press('Enter');
await page.locator('#scrim.on').waitFor();
assert.equal(await page.locator('#pName').textContent(), 'Abraham Lincoln');
await page.locator('#close').click();
await page.locator('#sigClear').click();
assert.equal(await page.locator('#sigSearch').inputValue(), '');
assert.equal(await page.locator('#grid .card').count(), total);
await page.locator('#sigSearch').fill('zzzz-no-signature-fixture');
assert.match(await page.locator('#empty').textContent(), /No signatures match/);
assert.equal(await page.locator('#ac').isVisible(), false);
assert.deepEqual(errors, []);
checks.push({ viewport:name, verdict:'PASS', paths:['Politics card -> selected clothing', 'four accessories for sale, two all-over shirts on hold', 'switch back to shirt restores purchase control', 'Politics details -> signature wallpaper studio', 'unknown clothing signature rejected', 'typeahead selection, clear and no-match recovery'], pageErrors:errors });
await context.close();
}
} finally { await browser.close(); }
fs.writeFileSync(path.join(artifacts,'result.json'), JSON.stringify({ base, timestamp:new Date().toISOString(), checks, externalMutations:0, artifacts },null,2));
console.log(JSON.stringify({ checks, artifacts },null,2));
})().catch(e => { console.error(e); process.exitCode=1; });