/** * 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. */ }); });