import assert from 'node:assert/strict'; import process from 'node:process'; const { chromium } = await import( process.env.PLAYWRIGHT_MODULE ?? 'playwright' ); const browser = await chromium.launch({ channel: 'chrome', headless: true }); const context = await browser.newContext({ reducedMotion: 'reduce' }); const page = await context.newPage(); page.setDefaultTimeout(10000); const errors = []; page.on('pageerror', (error) => errors.push(error.message)); const base = 'http://127.0.0.1:4198'; const result = (value) => context.addCookies([{ name: 'access_result', value, url: base }]); const visit = async (query = '') => { await result('success'); await page.goto(base + '/projects/1/access?reset' + query); await page .getByRole('heading', { name: 'Project access', exact: true }) .waitFor(); }; const button = (name) => page.getByRole('button', { name, exact: true }); const dialog = () => page.getByRole('dialog'); const checkbox = (name) => dialog().getByRole('checkbox', { name, exact: true }); const state = (value) => dialog() .locator('[data-form-state="' + value + '"]') .waitFor({ state: 'visible' }); const noOverflow = async () => assert.equal( await page.evaluate( () => document.documentElement.scrollWidth <= innerWidth, ), true, ); const save = async () => { const request = page.waitForRequest((r) => r.method() === 'PATCH'); await dialog() .getByRole('button', { name: 'Save permissions', exact: true }) .click(); return await request; }; try { for (const width of [1440, 390, 320]) { await page.setViewportSize({ width, height: 1000 }); for (const theme of ['light', 'dark']) { await page.emulateMedia({ colorScheme: theme }); await visit(); await noOverflow(); const trigger = button('Edit access for Sam Taylor'); await trigger.focus(); await page.keyboard.press('Enter'); await dialog().waitFor(); await noOverflow(); assert.equal(await checkbox('Read').isChecked(), true); assert.equal( await checkbox('Write').isChecked(), false, 'Inherited write must not be editable as a direct grant', ); await checkbox('Write').focus(); await page.keyboard.press('Space'); assert.equal(await checkbox('Write').isChecked(), true); await page.keyboard.press('Space'); await page.keyboard.press('Tab'); assert.equal( await dialog().evaluate((el) => el.contains(document.activeElement), ), true, ); await page.screenshot({ path: '/private/tmp/kan48-dialog-' + width + '-' + theme + '.png', }); await page.keyboard.press('Escape'); await dialog().waitFor({ state: 'hidden' }); assert.equal( await trigger.evaluate((el) => el === document.activeElement), true, ); await page.screenshot({ path: '/private/tmp/kan48-' + width + '-' + theme + '.png', fullPage: true, }); } } await page.locator('#access-search').fill('SAM@EXAMPLE'); assert.equal(await page.locator('[data-user-id]').count(), 1); await page.getByText('No matching teams', { exact: true }).waitFor(); await page.locator('#access-search').fill('unmatched'); await page.getByText('No matching people', { exact: true }).waitFor(); await visit('&empty&noUsers'); await page.getByText('No teams yet', { exact: true }).waitFor(); await page.getByText('No people available', { exact: true }).waitFor(); await visit(); await button('Edit access for Alex Morgan').click(); await dialog() .getByText('This person is a global administrator.', { exact: false }) .waitFor(); await page.keyboard.press('Escape'); await button('Edit access for Sam Taylor').click(); await checkbox('Read').uncheck(); const request = await save(); assert.equal( new URL(request.url()).pathname, '/projects/1/permissions/users/2', ); assert.deepEqual(request.postDataJSON(), { permissions: [] }); await dialog().waitFor({ state: 'hidden' }); await page .locator('[data-user-id="2"]') .getByText('Write', { exact: true }) .first() .waitFor(); await button('Edit access for Sam Taylor').click(); assert.equal(await checkbox('Read').isChecked(), false); await page.keyboard.press('Escape'); await button('Edit access for Platform team').click(); await checkbox('Write').uncheck(); await save(); await dialog().waitFor({ state: 'hidden' }); await page .locator('[data-user-id="2"]') .getByText('No project access', { exact: true }) .waitFor(); await button('Edit access for Sam Taylor').click(); await checkbox('Administrator').check(); await result('validation'); await save(); await state('validation'); await dialog() .getByText('This permission is unavailable.', { exact: true }) .waitFor(); assert.equal(await checkbox('Administrator').isChecked(), true); assert.equal( await dialog().locator('fieldset').getAttribute('aria-invalid'), 'true', ); await result('server'); await save(); await state('server'); await result('success'); await page.route('**/permissions/users/2', (route) => route.abort('failed'), ); await save(); await state('network'); await page.unroute('**/permissions/users/2'); await dialog() .getByRole('button', { name: 'Try again', exact: true }) .click(); await dialog().waitFor({ state: 'hidden' }); await page .locator('[data-user-id="2"]') .getByText('Administrator', { exact: true }) .first() .waitFor(); await button('Edit access for Sam Taylor').click(); assert.equal(await checkbox('Administrator').isChecked(), true); await result('forbidden'); await save(); await state('server'); await dialog() .getByText('Project access can no longer be changed', { exact: true }) .waitFor(); assert.equal( await dialog().getByRole('button', { name: 'Try again' }).count(), 0, ); await page.keyboard.press('Escape'); await visit(); await button('Edit access for Sam Taylor').click(); await checkbox('Write').check(); page.once('dialog', (d) => d.dismiss()); await page.keyboard.press('Escape'); assert.equal(await dialog().isVisible(), true); page.once('dialog', (d) => d.accept()); await page.keyboard.press('Escape'); await dialog().waitFor({ state: 'hidden' }); assert.deepEqual(errors, []); console.log( 'Project access browser checks passed: direct/team updates and clearing, effective access refresh, errors/retry, unsaved changes, keyboard, 320/390/1440px, both themes.', ); } finally { await browser.close(); }