WIP Feedback
This commit is contained in:
@@ -6,12 +6,13 @@
|
||||
* Run: ./run-screenshots.sh
|
||||
*/
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { injectMockDevice } from './helpers/mock-device.js';
|
||||
import { injectMockDevice, overrideFirmwareURLs } from './helpers/mock-device.js';
|
||||
import { hasFirmwareZip } from './helpers/assets.js';
|
||||
|
||||
const shot = async (page, name, testInfo) => {
|
||||
const shot = async (page, folder, name, testInfo) => {
|
||||
const project = testInfo.project.name;
|
||||
await page.waitForTimeout(200);
|
||||
await page.screenshot({ path: `screenshots/${project}/${name}.png`, fullPage: true });
|
||||
await page.screenshot({ path: `screenshots/${project}/${folder}/${name}.png`, fullPage: true });
|
||||
};
|
||||
|
||||
/** Dismiss the mobile warning modal if it's open. */
|
||||
@@ -23,143 +24,280 @@ const dismissMobileModal = async (page) => {
|
||||
}
|
||||
};
|
||||
|
||||
test('capture all steps', async ({ page }, testInfo) => {
|
||||
// ============================================================
|
||||
// 1. Manual NickelMenu flow
|
||||
// ============================================================
|
||||
|
||||
test('manual nickelmenu', async ({ page }, testInfo) => {
|
||||
const dir = 'manual-nickelmenu';
|
||||
const isMobile = testInfo.project.name === 'mobile';
|
||||
|
||||
// 1. Connect step (with mobile modal if applicable)
|
||||
await page.goto('/');
|
||||
if (isMobile) {
|
||||
// Capture the mobile warning modal
|
||||
await expect(page.locator('#mobile-dialog')).toBeVisible();
|
||||
await page.screenshot({ path: `screenshots/mobile/00-mobile-warning.png` });
|
||||
await page.click('#btn-mobile-continue');
|
||||
await expect(page.locator('#mobile-dialog')).not.toBeVisible();
|
||||
}
|
||||
await expect(page.locator('#step-connect')).not.toBeHidden();
|
||||
await injectMockDevice(page);
|
||||
await shot(page, '01-connect', testInfo);
|
||||
|
||||
// 2. Connection instructions
|
||||
await page.click('#btn-connect');
|
||||
await expect(page.locator('#step-connect-instructions')).not.toBeHidden();
|
||||
await shot(page, '02-connect-instructions', testInfo);
|
||||
|
||||
// 2b. Connection instructions with disclaimer open
|
||||
await page.click('details.banner--accent summary');
|
||||
await page.waitForTimeout(100);
|
||||
await shot(page, '03-connect-instructions-disclaimer', testInfo);
|
||||
|
||||
// 3. Device detected
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, '04-device', testInfo);
|
||||
|
||||
// 4. Mode selection
|
||||
await page.click('#btn-device-next');
|
||||
// Click "Build downloadable archive" to enter manual mode
|
||||
await page.click('#btn-manual');
|
||||
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||
await shot(page, '05-mode-selection', testInfo);
|
||||
await shot(page, dir, '01-mode-selection', testInfo);
|
||||
|
||||
// 5a. NickelMenu config
|
||||
// Select NickelMenu → config
|
||||
await page.click('input[name="mode"][value="nickelmenu"]');
|
||||
await page.click('#btn-mode-next');
|
||||
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
|
||||
await shot(page, '06-nickelmenu-config', testInfo);
|
||||
await shot(page, dir, '02-nickelmenu-config', testInfo);
|
||||
|
||||
// 5b. NickelMenu features (preset)
|
||||
// Preset → features
|
||||
await page.click('input[value="preset"]');
|
||||
await page.click('#btn-nm-next');
|
||||
await expect(page.locator('#step-nm-features')).not.toBeHidden();
|
||||
await shot(page, '07-nickelmenu-features', testInfo);
|
||||
await shot(page, dir, '03-nickelmenu-features', testInfo);
|
||||
|
||||
// 5c. NickelMenu review
|
||||
// Features → review (only download button in manual mode)
|
||||
await page.click('#btn-nm-features-next');
|
||||
await expect(page.locator('#step-nm-review')).not.toBeHidden();
|
||||
await shot(page, '08-nickelmenu-review', testInfo);
|
||||
await shot(page, dir, '04-nickelmenu-review', testInfo);
|
||||
|
||||
// Go back to mode and try patches path
|
||||
await page.click('#btn-nm-review-back');
|
||||
await page.click('#btn-nm-features-back');
|
||||
await page.click('#btn-nm-back');
|
||||
// Download → done
|
||||
await page.click('#btn-nm-download');
|
||||
const nmDone = page.locator('#step-nm-done');
|
||||
await expect(nmDone).not.toBeHidden();
|
||||
await shot(page, dir, '05-nickelmenu-done', testInfo);
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 2. Manual Patches flow
|
||||
// ============================================================
|
||||
|
||||
test('manual patches', async ({ page }, testInfo) => {
|
||||
test.skip(!hasFirmwareZip(), 'Firmware zip not available');
|
||||
|
||||
const dir = 'manual-patches';
|
||||
const isMobile = testInfo.project.name === 'mobile';
|
||||
|
||||
await page.goto('/');
|
||||
await injectMockDevice(page);
|
||||
await page.waitForFunction(() => !!window.FIRMWARE_DOWNLOADS);
|
||||
await overrideFirmwareURLs(page);
|
||||
|
||||
if (isMobile) {
|
||||
await page.click('#btn-mobile-continue');
|
||||
await expect(page.locator('#mobile-dialog')).not.toBeVisible();
|
||||
}
|
||||
|
||||
// Click "Build downloadable archive" to enter manual mode
|
||||
await page.click('#btn-manual');
|
||||
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||
await shot(page, dir, '01-mode-selection', testInfo);
|
||||
|
||||
// Select Patches → version selection
|
||||
await page.click('input[name="mode"][value="patches"]');
|
||||
await page.click('#btn-mode-next');
|
||||
await expect(page.locator('#step-patches')).not.toBeHidden();
|
||||
await shot(page, '09-patches-config', testInfo);
|
||||
await expect(page.locator('#step-manual-version')).not.toBeHidden();
|
||||
await shot(page, dir, '02-version-selection', testInfo);
|
||||
|
||||
// 6b. Expand a patch section and select a patch
|
||||
// Select firmware version and model
|
||||
await page.selectOption('#manual-version', { index: 1 });
|
||||
await expect(page.locator('#manual-model')).not.toBeHidden();
|
||||
await page.selectOption('#manual-model', { index: 1 });
|
||||
await page.click('#btn-manual-confirm');
|
||||
|
||||
// Patches config
|
||||
await expect(page.locator('#step-patches')).not.toBeHidden();
|
||||
await shot(page, dir, '03-patches-config', testInfo);
|
||||
|
||||
// Expand section and select a patch
|
||||
const section = page.locator('.patch-file-section').first();
|
||||
await section.locator('summary').click();
|
||||
const patchLabel = section.locator('label').filter({ has: page.locator('.patch-name:not(.patch-name-none)') }).first();
|
||||
await patchLabel.locator('input').check();
|
||||
await shot(page, '10-patches-selected', testInfo);
|
||||
await shot(page, dir, '04-patches-selected', testInfo);
|
||||
|
||||
// Review & build
|
||||
await page.click('#btn-patches-next');
|
||||
await expect(page.locator('#step-firmware')).not.toBeHidden();
|
||||
await shot(page, dir, '05-build', testInfo);
|
||||
|
||||
// Build
|
||||
await page.click('#btn-build');
|
||||
const stepDone = page.locator('#step-done');
|
||||
await expect(stepDone).not.toBeHidden({ timeout: 60_000 });
|
||||
await shot(page, dir, '06-patches-done', testInfo);
|
||||
|
||||
// Download
|
||||
await page.click('#btn-download');
|
||||
await expect(stepDone.locator('#download-instructions')).toBeVisible();
|
||||
await shot(page, dir, '07-patches-done-download', testInfo);
|
||||
});
|
||||
|
||||
test('nickelmenu done with feedback', async ({ page }, testInfo) => {
|
||||
// Enable analytics so the feedback widget appears
|
||||
await page.addInitScript(() => { window.__ANALYTICS_ENABLED = true; });
|
||||
// ============================================================
|
||||
// 3. Connected NickelMenu flow
|
||||
// ============================================================
|
||||
|
||||
test('connected nickelmenu', async ({ page }, testInfo) => {
|
||||
const dir = 'connected-nickelmenu';
|
||||
const isMobile = testInfo.project.name === 'mobile';
|
||||
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
if (isMobile) {
|
||||
await expect(page.locator('#mobile-dialog')).toBeVisible();
|
||||
await page.screenshot({ path: `screenshots/mobile/${dir}/00-mobile-warning.png` });
|
||||
await page.click('#btn-mobile-continue');
|
||||
}
|
||||
await expect(page.locator('#step-connect')).not.toBeHidden();
|
||||
await injectMockDevice(page);
|
||||
await shot(page, dir, '01-connect', testInfo);
|
||||
|
||||
// Connect device → mode selection → NickelMenu
|
||||
// Connection instructions
|
||||
await page.click('#btn-connect');
|
||||
await expect(page.locator('#step-connect-instructions')).not.toBeHidden();
|
||||
await shot(page, dir, '02-connect-instructions', testInfo);
|
||||
|
||||
// Device detected
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, dir, '03-device', testInfo);
|
||||
|
||||
// Mode selection
|
||||
await page.click('#btn-device-next');
|
||||
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||
await shot(page, dir, '04-mode-selection', testInfo);
|
||||
|
||||
// NickelMenu config
|
||||
await page.click('input[name="mode"][value="nickelmenu"]');
|
||||
await page.click('#btn-mode-next');
|
||||
await expect(page.locator('#step-nickelmenu')).not.toBeHidden();
|
||||
await shot(page, dir, '05-nickelmenu-config', testInfo);
|
||||
|
||||
// NickelMenu-only → review → write to device
|
||||
await page.click('input[value="nickelmenu-only"]');
|
||||
// Preset → features
|
||||
await page.click('input[value="preset"]');
|
||||
await page.click('#btn-nm-next');
|
||||
await expect(page.locator('#step-nm-review')).not.toBeHidden();
|
||||
await page.click('#btn-nm-write');
|
||||
await expect(page.locator('#step-nm-features')).not.toBeHidden();
|
||||
await shot(page, dir, '06-nickelmenu-features', testInfo);
|
||||
|
||||
// Wait for done step
|
||||
// Features → review
|
||||
await page.click('#btn-nm-features-next');
|
||||
await expect(page.locator('#step-nm-review')).not.toBeHidden();
|
||||
await shot(page, dir, '07-nickelmenu-review', testInfo);
|
||||
|
||||
// Write to device → done
|
||||
await page.click('#btn-nm-write');
|
||||
const nmDone = page.locator('#step-nm-done');
|
||||
await expect(nmDone).not.toBeHidden();
|
||||
await expect(nmDone.locator('.feedback')).toBeVisible();
|
||||
await shot(page, '15-done-feedback', testInfo);
|
||||
await shot(page, dir, '08-nickelmenu-done', testInfo);
|
||||
});
|
||||
|
||||
// Click thumbs up and capture the thank-you state
|
||||
await nmDone.locator('.feedback-btn[data-vote="up"]').click();
|
||||
await expect(nmDone.locator('.feedback-thanks')).toBeVisible();
|
||||
await shot(page, '16-done-feedback-voted', testInfo);
|
||||
// ============================================================
|
||||
// 4. Connected Patches flow
|
||||
// ============================================================
|
||||
|
||||
test('connected patches', async ({ page }, testInfo) => {
|
||||
test.skip(!hasFirmwareZip(), 'Firmware zip not available');
|
||||
|
||||
const dir = 'connected-patches';
|
||||
const isMobile = testInfo.project.name === 'mobile';
|
||||
|
||||
await page.goto('/');
|
||||
await injectMockDevice(page);
|
||||
await page.waitForFunction(() => !!window.FIRMWARE_DOWNLOADS);
|
||||
await overrideFirmwareURLs(page);
|
||||
|
||||
if (isMobile) {
|
||||
await page.click('#btn-mobile-continue');
|
||||
await expect(page.locator('#mobile-dialog')).not.toBeVisible();
|
||||
}
|
||||
|
||||
await expect(page.locator('#step-connect')).not.toBeHidden();
|
||||
await shot(page, dir, '01-connect', testInfo);
|
||||
|
||||
// Connection instructions
|
||||
await page.click('#btn-connect');
|
||||
await expect(page.locator('#step-connect-instructions')).not.toBeHidden();
|
||||
await shot(page, dir, '02-connect-instructions', testInfo);
|
||||
|
||||
// Device detected
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, dir, '03-device', testInfo);
|
||||
|
||||
// Mode selection
|
||||
await page.click('#btn-device-next');
|
||||
await expect(page.locator('#step-mode')).not.toBeHidden();
|
||||
await shot(page, dir, '04-mode-selection', testInfo);
|
||||
|
||||
// Patches config
|
||||
await page.click('input[name="mode"][value="patches"]');
|
||||
await page.click('#btn-mode-next');
|
||||
await expect(page.locator('#step-patches')).not.toBeHidden();
|
||||
await shot(page, dir, '05-patches-config', testInfo);
|
||||
|
||||
// Expand section and select a patch
|
||||
const section = page.locator('.patch-file-section').first();
|
||||
await section.locator('summary').click();
|
||||
const patchLabel = section.locator('label').filter({ has: page.locator('.patch-name:not(.patch-name-none)') }).first();
|
||||
await patchLabel.locator('input').check();
|
||||
await shot(page, dir, '06-patches-selected', testInfo);
|
||||
|
||||
// Review & build
|
||||
await page.click('#btn-patches-next');
|
||||
await expect(page.locator('#step-firmware')).not.toBeHidden();
|
||||
await shot(page, dir, '07-build', testInfo);
|
||||
|
||||
// Build → done
|
||||
await page.click('#btn-build');
|
||||
const stepDone = page.locator('#step-done');
|
||||
await expect(stepDone).not.toBeHidden({ timeout: 60_000 });
|
||||
await shot(page, dir, '08-patches-done', testInfo);
|
||||
|
||||
// Download
|
||||
await page.click('#btn-download');
|
||||
await expect(stepDone.locator('#download-instructions')).toBeVisible();
|
||||
await shot(page, dir, '09-patches-done-download', testInfo);
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// 5. Edge cases
|
||||
// ============================================================
|
||||
|
||||
test('unsupported browser', async ({ page }, testInfo) => {
|
||||
const dir = 'edge-cases';
|
||||
await page.addInitScript(() => { delete window.showDirectoryPicker; });
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await expect(page.locator('#connect-unsupported-hint')).toBeVisible();
|
||||
await shot(page, dir, 'unsupported-browser', testInfo);
|
||||
});
|
||||
|
||||
test('incompatible firmware', async ({ page }, testInfo) => {
|
||||
const dir = 'edge-cases';
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await injectMockDevice(page, { firmware: '5.0.0' });
|
||||
await page.click('#btn-connect');
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, '11-device-incompatible', testInfo);
|
||||
await shot(page, dir, 'incompatible-firmware', testInfo);
|
||||
});
|
||||
|
||||
test('unknown model', async ({ page }, testInfo) => {
|
||||
const dir = 'edge-cases';
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await injectMockDevice(page, { serial: 'X9990A0000000' });
|
||||
await page.click('#btn-connect');
|
||||
await page.click('#btn-connect-ready');
|
||||
await expect(page.locator('#step-device')).not.toBeHidden();
|
||||
await shot(page, '12-device-unknown', testInfo);
|
||||
});
|
||||
|
||||
test('unsupported browser', async ({ page }, testInfo) => {
|
||||
await page.addInitScript(() => { delete window.showDirectoryPicker; });
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await expect(page.locator('#connect-unsupported-hint')).toBeVisible();
|
||||
await shot(page, '13-connect-unsupported', testInfo);
|
||||
await shot(page, dir, 'unknown-model', testInfo);
|
||||
});
|
||||
|
||||
test('disclaimer dialog', async ({ page }, testInfo) => {
|
||||
const dir = 'edge-cases';
|
||||
await page.goto('/');
|
||||
await dismissMobileModal(page);
|
||||
await page.click('#btn-how-it-works');
|
||||
await expect(page.locator('#how-it-works-dialog')).toBeVisible();
|
||||
await page.waitForTimeout(200);
|
||||
await page.screenshot({ path: `screenshots/${testInfo.project.name}/14-disclaimer-dialog.png` });
|
||||
await page.screenshot({ path: `screenshots/${testInfo.project.name}/${dir}/disclaimer-dialog.png` });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user