281 lines
10 KiB
JavaScript
281 lines
10 KiB
JavaScript
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 base = 'http://127.0.0.1:4188';
|
|
const context = await browser.newContext({
|
|
viewport: { width: 1440, height: 1000 },
|
|
});
|
|
const page = await context.newPage();
|
|
page.setDefaultTimeout(10000);
|
|
const errors = [];
|
|
page.on('pageerror', (error) => errors.push(error.message));
|
|
const requests = [];
|
|
page.on('request', (request) => {
|
|
if (['POST', 'PATCH', 'DELETE'].includes(request.method())) {
|
|
requests.push(request);
|
|
}
|
|
});
|
|
const button = (name) => page.getByRole('button', { name, exact: true });
|
|
const dialog = () => page.getByRole('dialog');
|
|
const result = (value) =>
|
|
context.addCookies([{ name: 'workflow_test_result', value, url: base }]);
|
|
const visit = async (query = '?reset') => {
|
|
await result('success');
|
|
await page.goto(base + '/projects/1/settings/board' + query);
|
|
await page
|
|
.getByRole('heading', { name: 'Board workflow', exact: true })
|
|
.waitFor();
|
|
};
|
|
const noOverflow = async () =>
|
|
assert.equal(
|
|
await page.evaluate(
|
|
() => document.documentElement.scrollWidth <= window.innerWidth,
|
|
),
|
|
true,
|
|
);
|
|
const submit = async (label, method, pathname, expected) => {
|
|
const sent = page.waitForRequest((request) => request.method() === method);
|
|
await dialog().getByRole('button', { name: label, exact: true }).click();
|
|
const request = await sent;
|
|
assert.equal(new URL(request.url()).pathname, pathname);
|
|
assert.deepEqual(request.postDataJSON(), expected);
|
|
};
|
|
const closed = () => dialog().waitFor({ state: 'hidden' });
|
|
const formState = (state) =>
|
|
dialog()
|
|
.locator('[data-form-state="' + state + '"]')
|
|
.waitFor({ state: 'visible' });
|
|
try {
|
|
for (const width of [1440, 768, 390, 320]) {
|
|
await page.setViewportSize({ width, height: 1000 });
|
|
for (const theme of ['light', 'dark']) {
|
|
await page.emulateMedia({ colorScheme: theme });
|
|
await visit();
|
|
await noOverflow();
|
|
assert.equal(
|
|
await button('Delete status Backlog').isDisabled(),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
await button('Delete status Ready').isDisabled(),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
await button('Delete status Review').isDisabled(),
|
|
false,
|
|
);
|
|
const trigger = button('Edit column In progress');
|
|
await trigger.focus();
|
|
await page.keyboard.press('Enter');
|
|
await dialog().waitFor();
|
|
assert.equal(
|
|
await page
|
|
.getByLabel('Column name', { exact: true })
|
|
.inputValue(),
|
|
'In progress',
|
|
);
|
|
await page.keyboard.press('Tab');
|
|
assert.equal(
|
|
await dialog().evaluate((element) =>
|
|
element.contains(document.activeElement),
|
|
),
|
|
true,
|
|
);
|
|
await noOverflow();
|
|
await page.keyboard.press('Escape');
|
|
await closed();
|
|
assert.equal(
|
|
await trigger.evaluate(
|
|
(element) => document.activeElement === element,
|
|
),
|
|
true,
|
|
);
|
|
if ([390, 1440].includes(width)) {
|
|
await page.screenshot({
|
|
path: '/private/tmp/KAN-40-' + width + '-' + theme + '.png',
|
|
fullPage: true,
|
|
});
|
|
}
|
|
assert.equal(requests.length, 0);
|
|
}
|
|
}
|
|
await visit('?reset&empty');
|
|
await page.getByText('No board columns yet', { exact: true }).waitFor();
|
|
await visit();
|
|
await button('Add column').click();
|
|
await page
|
|
.getByLabel('Column name', { exact: true })
|
|
.fill('Quality assurance');
|
|
await result('validation');
|
|
await submit('Add column', 'POST', '/projects/1/settings/board/columns', {
|
|
name: 'Quality assurance',
|
|
});
|
|
await formState('validation');
|
|
assert.equal(
|
|
await page.getByLabel('Column name', { exact: true }).inputValue(),
|
|
'Quality assurance',
|
|
);
|
|
assert.equal(
|
|
await page
|
|
.getByLabel('Column name', { exact: true })
|
|
.getAttribute('aria-invalid'),
|
|
'true',
|
|
);
|
|
await result('server');
|
|
await dialog()
|
|
.getByRole('button', { name: 'Add column', exact: true })
|
|
.click();
|
|
await formState('server');
|
|
await result('success');
|
|
await dialog()
|
|
.getByRole('button', { name: 'Try again', exact: true })
|
|
.click();
|
|
await closed();
|
|
await button('Edit column Quality assurance').waitFor();
|
|
const newColumnId = await page
|
|
.locator('[data-column-id]')
|
|
.last()
|
|
.getAttribute('data-column-id');
|
|
await button('Edit column Quality assurance').click();
|
|
await page.getByLabel('Column name', { exact: true }).fill('QA');
|
|
await page.getByLabel('Position', { exact: true }).fill('1');
|
|
await result('position');
|
|
await dialog().getByRole('button', { name: 'Save', exact: true }).click();
|
|
await formState('validation');
|
|
assert.equal(
|
|
await page
|
|
.getByLabel('Position', { exact: true })
|
|
.getAttribute('aria-invalid'),
|
|
'true',
|
|
);
|
|
await result('success');
|
|
await submit(
|
|
'Save',
|
|
'PATCH',
|
|
'/projects/1/settings/board/columns/' + newColumnId,
|
|
{ name: 'QA', position: 1 },
|
|
);
|
|
await closed();
|
|
assert.equal(
|
|
await page
|
|
.locator('[data-column-id]')
|
|
.first()
|
|
.getAttribute('data-column-id'),
|
|
newColumnId,
|
|
);
|
|
await button('Edit column QA').click();
|
|
assert.equal(
|
|
await page.getByLabel('Position', { exact: true }).inputValue(),
|
|
'1',
|
|
);
|
|
assert.equal(
|
|
await page.getByLabel('Column name', { exact: true }).inputValue(),
|
|
'QA',
|
|
);
|
|
await page.getByLabel('Column name', { exact: true }).fill('Unsaved');
|
|
page.once('dialog', (prompt) => prompt.dismiss());
|
|
await dialog().getByRole('button', { name: 'Cancel', exact: true }).click();
|
|
assert.equal(await dialog().isVisible(), true);
|
|
page.once('dialog', (prompt) => prompt.accept());
|
|
await dialog().getByRole('button', { name: 'Cancel', exact: true }).click();
|
|
await closed();
|
|
await button('Add status to Backlog').click();
|
|
assert.equal(
|
|
await page.getByLabel('Status name', { exact: true }).inputValue(),
|
|
'',
|
|
);
|
|
await page.getByLabel('Status name', { exact: true }).fill('Triage');
|
|
await submit('Add status', 'POST', '/projects/1/settings/board/statuses', {
|
|
name: 'Triage',
|
|
board_column_id: null,
|
|
});
|
|
await closed();
|
|
await button('Rename status Triage').waitFor();
|
|
await button('Add status to Todo').click();
|
|
await page.getByLabel('Status name', { exact: true }).fill('Refinement');
|
|
await submit('Add status', 'POST', '/projects/1/settings/board/statuses', {
|
|
name: 'Refinement',
|
|
board_column_id: 10,
|
|
});
|
|
await closed();
|
|
await button('Rename status Review').click();
|
|
await page.getByLabel('Status name', { exact: true }).fill('Peer review');
|
|
await page.route('**/statuses/4', (route) => route.abort('failed'));
|
|
await dialog().getByRole('button', { name: 'Save', exact: true }).click();
|
|
await formState('network');
|
|
await page.unroute('**/statuses/4');
|
|
await submit(
|
|
'Try again',
|
|
'PATCH',
|
|
'/projects/1/settings/board/statuses/4',
|
|
{ name: 'Peer review' },
|
|
);
|
|
await closed();
|
|
const before = requests.length;
|
|
await button('Delete status Peer review').click();
|
|
await dialog()
|
|
.getByText(/assigned to “Building”/)
|
|
.waitFor();
|
|
await dialog().getByRole('button', { name: 'Cancel', exact: true }).click();
|
|
await closed();
|
|
assert.equal(requests.length, before);
|
|
await button('Delete status Peer review').click();
|
|
await result('validation');
|
|
await submit(
|
|
'Delete',
|
|
'DELETE',
|
|
'/projects/1/settings/board/statuses/4',
|
|
{},
|
|
);
|
|
await formState('validation');
|
|
await dialog()
|
|
.getByText('This group needs at least one status.', { exact: true })
|
|
.waitFor();
|
|
await result('success');
|
|
await dialog().getByRole('button', { name: 'Delete', exact: true }).click();
|
|
await closed();
|
|
assert.equal(await button('Delete status Building').isDisabled(), true);
|
|
await button('Delete column In progress').click();
|
|
await dialog()
|
|
.getByText(/statuses move to the backlog/)
|
|
.waitFor();
|
|
await submit(
|
|
'Delete',
|
|
'DELETE',
|
|
'/projects/1/settings/board/columns/20',
|
|
{},
|
|
);
|
|
await closed();
|
|
await page
|
|
.getByRole('list', { name: 'Statuses in Backlog', exact: true })
|
|
.getByText('Building', { exact: true })
|
|
.waitFor();
|
|
assert.equal(await button('Edit column In progress').count(), 0);
|
|
await page.locator('summary').filter({ hasText: 'Status moves' }).click();
|
|
const transition = page.locator('details form').first();
|
|
const checkbox = transition.getByRole('checkbox').first();
|
|
await checkbox.check();
|
|
const move = page.waitForRequest((request) => request.method() === 'POST');
|
|
await transition.getByRole('button', { name: 'Save', exact: true }).click();
|
|
assert.equal(
|
|
new URL((await move).url()).pathname,
|
|
'/projects/1/settings/board/statuses/1/transitions',
|
|
);
|
|
assert.equal(
|
|
new URL((await move).url()).searchParams.get('_method'),
|
|
'PATCH',
|
|
);
|
|
assert.deepEqual((await move).postDataJSON(), {
|
|
allowed_ticket_status_ids: [await checkbox.getAttribute('value')],
|
|
});
|
|
assert.deepEqual(errors, []);
|
|
console.log(
|
|
'Workflow browser checks passed: CRUD payloads, server refresh, order, deletion protection, validation, retries, dirty forms, keyboard, responsive widths and both themes.',
|
|
);
|
|
} finally {
|
|
await browser.close();
|
|
}
|