Root cause fix: tests/_helpers/ae_defaults.ts was missing __version: 1, causing store_versions.ts to wipe ae_loc from localStorage on every test page load. This made trusted_access fall back to false, hiding the print button (can_print guard) and failing all attendee workflow tests. Changes: - ae_defaults.ts: add __version: 1 with comment explaining the store_versions guard - idb_helpers.ts: extract inject_badge_and_template() from print layout test into shared helper; now used by all three print/render test files - event_badge_render.test.ts: new — 4 tests covering full_name_override priority, full_name fallback, duplex=0 hides badge back, duplex=1 shows badge back - event_badge_attendee_workflow.test.ts: cleaned up (diagnostic code removed); all 3 tests now pass - event_badge_print_layout.test.ts: renamed from badge_print_layout.test.ts; inline inject_idb() replaced with shared idb_helpers import - event_badge_smoke.test.ts: renamed from event_badge.test.ts - playwright.config.ts: use system /usr/bin/chromium on Arch Linux (avoids Playwright's bundled Chromium which requires Ubuntu libs not present on Arch) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
171 lines
7.9 KiB
TypeScript
171 lines
7.9 KiB
TypeScript
/**
|
|
* Badge Print Page — Attendee Print Workflow Tests
|
|
*
|
|
* Verifies the real staff-facing print workflow:
|
|
* Badge list → click badge → /print page renders → Print Badge button → PATCH → navigate back
|
|
*
|
|
* There is no separate "badge detail" page. From the badge list, clicking a badge
|
|
* goes directly to /events/{event_id}/badges/{badge_id}/print.
|
|
*
|
|
* Strategy: inject badge + template into IDB (same pattern as event_badge_print_layout.test.ts),
|
|
* then interact with the print page controls.
|
|
*/
|
|
import { test, expect } from '@playwright/test';
|
|
import { ae_app_local_data_defaults } from './_helpers/ae_defaults';
|
|
import { testing_event_id, testing_account_id, mock_site_domain } from './_helpers/env';
|
|
import { inject_badge_and_template } from './_helpers/idb_helpers';
|
|
|
|
const event_id = testing_event_id;
|
|
const badge_id = 'UIJT-73-63-61';
|
|
const template_id = 'jgfixEpYp1B';
|
|
|
|
const mock_badge = {
|
|
id: badge_id,
|
|
event_badge_id: badge_id,
|
|
event_badge_id_random: badge_id,
|
|
event_id: event_id,
|
|
event_id_random: event_id,
|
|
event_badge_template_id: template_id,
|
|
event_badge_template_id_random: template_id,
|
|
full_name_override: 'Scott Idem',
|
|
given_name: 'Scott',
|
|
family_name: 'Idem',
|
|
email: 'scott@example.com',
|
|
badge_type: 'presenter',
|
|
badge_type_code: 'presenter',
|
|
print_count: 0,
|
|
affiliations: 'One Sky IT',
|
|
location: 'Minneapolis, MN',
|
|
enable: true,
|
|
};
|
|
|
|
const mock_template = {
|
|
id: template_id,
|
|
id_random: template_id,
|
|
event_badge_template_id: template_id,
|
|
badge_template_id: template_id,
|
|
event_id: event_id,
|
|
event_id_random: event_id,
|
|
name: 'Dev Demo 202x',
|
|
layout: 'badge_3.5x5.5_pvc',
|
|
cfg_json: '{}',
|
|
duplex: 1,
|
|
enable: true,
|
|
};
|
|
|
|
test.describe('Badge Print Page — attendee print workflow', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
|
|
|
|
await page.route('**/v3/**', async (route) => {
|
|
const url = route.request().url();
|
|
const method = route.request().method();
|
|
|
|
if (url.includes('site_domain/search')) {
|
|
return route.fulfill({ status: 200, contentType: 'application/json',
|
|
body: JSON.stringify({ data: [mock_site_domain] }) });
|
|
}
|
|
if (url.includes(`/v3/crud/event/${event_id}`) && !url.includes('event_badge') && method === 'GET') {
|
|
return route.fulfill({ status: 200, contentType: 'application/json',
|
|
body: JSON.stringify({ data: {
|
|
id: event_id, event_id, name: 'Test Event',
|
|
cfg_json: {}, mod_badges_json: {}, mod_pres_mgmt_json: {},
|
|
mod_abstracts_json: {}, mod_exhibits_json: {}, mod_meetings_json: {},
|
|
}}) });
|
|
}
|
|
// Badge PATCH — print_count update from the print button
|
|
if (url.includes(`event_badge/${badge_id}`) && (method === 'PATCH' || method === 'PUT')) {
|
|
const body = JSON.parse((await route.request().postData()) ?? '{}');
|
|
return route.fulfill({ status: 200, contentType: 'application/json',
|
|
body: JSON.stringify({ data: { ...mock_badge, ...body } }) });
|
|
}
|
|
return route.fulfill({ status: 200, contentType: 'application/json',
|
|
body: JSON.stringify({ data: [] }) });
|
|
});
|
|
|
|
await page.addInitScript(([defaults, e_id, a_id]) => {
|
|
const data = {
|
|
...defaults,
|
|
account_id: a_id,
|
|
allow_access: true,
|
|
authenticated_access: true,
|
|
trusted_access: true,
|
|
manager_access: true,
|
|
edit_mode: false,
|
|
} as any;
|
|
data.mod = { ...defaults.mod, events: { ...defaults.mod.events, event_id: e_id } };
|
|
window.localStorage.setItem('ae_loc', JSON.stringify(data));
|
|
}, [ae_app_local_data_defaults, event_id, testing_account_id] as const);
|
|
});
|
|
|
|
test('print page renders badge name from IDB', async ({ page }) => {
|
|
await page.goto(`/events/${event_id}/badges/${badge_id}/print`);
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
await page.evaluate(inject_badge_and_template, { badge: mock_badge, template: mock_template });
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 });
|
|
|
|
// Badge header should display the attendee name
|
|
const name_text = await page.locator('.full_name_override').textContent();
|
|
expect(name_text?.trim()).toBe('Scott Idem');
|
|
|
|
// Print button should be visible (trusted user, print_count=0)
|
|
await expect(page.locator('[data-testid="badge-print-btn"]')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test('print button sends PATCH with incremented print_count', async ({ page }) => {
|
|
await page.goto(`/events/${event_id}/badges/${badge_id}/print`);
|
|
await page.waitForLoadState('domcontentloaded');
|
|
await page.evaluate(inject_badge_and_template, { badge: mock_badge, template: mock_template });
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 });
|
|
await page.waitForSelector('[data-testid="badge-print-btn"]', { timeout: 5000 });
|
|
|
|
const patch_promise = page.waitForRequest(
|
|
(r) => r.url().includes(`event_badge/${badge_id}`) &&
|
|
(r.method() === 'PATCH' || r.method() === 'PUT'),
|
|
{ timeout: 10000 }
|
|
);
|
|
|
|
await page.locator('[data-testid="badge-print-btn"]').click();
|
|
|
|
const patch_req = await patch_promise;
|
|
const body = JSON.parse(patch_req.postData() ?? '{}');
|
|
|
|
expect(body.print_count, 'print_count should increment to 1').toBe(1);
|
|
expect(body.print_last_datetime, 'print_last_datetime should be set').toBeDefined();
|
|
expect(body.print_first_datetime,'print_first_datetime set on first print').toBeDefined();
|
|
});
|
|
|
|
test('print button navigates back to badge search after printing', async ({ page }) => {
|
|
await page.goto(`/events/${event_id}/badges/${badge_id}/print`);
|
|
await page.waitForLoadState('domcontentloaded');
|
|
await page.evaluate(inject_badge_and_template, { badge: mock_badge, template: mock_template });
|
|
await page.reload();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
await page.waitForSelector('.event_badge_wrapper', { timeout: 8000 });
|
|
await page.waitForSelector('[data-testid="badge-print-btn"]', { timeout: 5000 });
|
|
|
|
await page.locator('[data-testid="badge-print-btn"]').click();
|
|
|
|
// handle_print_badge waits ~1s after PATCH then does window.location.href = /badges
|
|
await expect(page).toHaveURL(new RegExp(`/events/${event_id}/badges$`), { timeout: 8000 });
|
|
await expect(page.locator('#badge_fulltext_search_qry_str')).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test.skip('future: attendee self-review via email link', () => {
|
|
/*
|
|
* Attendee receives an email link to /events/{event_id}/badges/{badge_id}/review.
|
|
* They can view and edit their own name, title, affiliations, location.
|
|
* Token is time-limited and single-use.
|
|
* Not yet implemented — review page exists but email dispatch does not.
|
|
*/
|
|
});
|
|
});
|