wayBetter for X
This commit is contained in:
commit
df5dff619e
720 changed files with 147465 additions and 0 deletions
149
apps/desktop/scripts/accounts-ux-shots.mjs
Normal file
149
apps/desktop/scripts/accounts-ux-shots.mjs
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
// Account switcher screenshots and flow checks on the shell preview harness (`/?shell`, needs `vite dev`).
|
||||
// Usage: node scripts/accounts-ux-shots.mjs [baseUrl] [outDir] [prefix]
|
||||
// prefix "before" only takes screenshots; any other prefix also checks the click flow.
|
||||
import { chromium } from 'playwright';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const base = process.argv[2] ?? 'http://127.0.0.1:1420';
|
||||
const out = process.argv[3] ?? join(import.meta.dirname, '..', '..', '..', 'verification', 'accounts-ux');
|
||||
const prefix = process.argv[4] ?? 'after';
|
||||
mkdirSync(out, { recursive: true });
|
||||
|
||||
const WIDE = { width: 1440, height: 860 };
|
||||
const failures = [];
|
||||
const check = (label, ok, detail = '') => { console.log(`${ok ? 'ok ' : 'FAIL'} ${label}${detail ? ` (${detail})` : ''}`); if (!ok) failures.push(label); };
|
||||
|
||||
const browser = await chromium.launch();
|
||||
async function open(query, viewport = WIDE) {
|
||||
const page = await browser.newPage({ viewport });
|
||||
page.on('pageerror', error => failures.push(`page error: ${error.message}`));
|
||||
await page.goto(`${base}/?shell&${query}`);
|
||||
await page.waitForSelector('.bt-profile-switcher');
|
||||
await page.waitForTimeout(400);
|
||||
return page;
|
||||
}
|
||||
const layout = page => page.evaluate(() => window.__btShellLayout());
|
||||
const shot = (page, name) => page.screenshot({ path: join(out, `${prefix}-${name}.png`) });
|
||||
|
||||
// Menu open, 4 accounts (two signed in, one signed out, one never signed in), split view.
|
||||
let page = await open('tabs=5&profiles=4&split&open=accounts');
|
||||
await page.waitForTimeout(300);
|
||||
await shot(page, 'menu');
|
||||
await page.close();
|
||||
|
||||
page = await open('tabs=5&profiles=4&split');
|
||||
await shot(page, 'panes');
|
||||
|
||||
if (prefix !== 'before') {
|
||||
// The focused pane's account is the one the titlebar shows and highlights.
|
||||
const focusedTab = await page.evaluate(() => window.__btShellLayout().active);
|
||||
const title = await page.getAttribute('.bt-profile-switcher', 'aria-label');
|
||||
check('titlebar names the focused pane’s account', /CRT kafa|Sample|Shitposts|New account/.test(title ?? ''), `${focusedTab}: ${title}`);
|
||||
|
||||
// Click an account that has no visible pane → a new pane on the right with its home feed.
|
||||
await page.click('.bt-profile-switcher');
|
||||
await page.waitForSelector('.bt-acct-menu');
|
||||
const before = await layout(page);
|
||||
await page.click('.bt-acct-row[data-profile="pnews01"] .bt-acct-main');
|
||||
await page.waitForTimeout(300);
|
||||
let after = await layout(page);
|
||||
check('never-signed-in account opens X sign-in in a new pane', after.groups.length === before.groups.length + 1
|
||||
&& after.groups.some(group => group.urls.includes('https://x.com/i/flow/login')), JSON.stringify(after.groups.map(g => g.urls)));
|
||||
check('menu closes after a pick', !(await page.$('.bt-acct-menu')));
|
||||
|
||||
// Click an account whose pane is visible → that pane is focused, nothing new opens.
|
||||
const visibleDefault = await page.evaluate(() => {
|
||||
const api = window.__btShellLayout();
|
||||
return api.groups.map(group => group.active);
|
||||
});
|
||||
await page.click('.bt-profile-switcher');
|
||||
await page.click('.bt-acct-row[data-profile="default"] .bt-acct-main');
|
||||
await page.waitForTimeout(250);
|
||||
const refocus = await layout(page);
|
||||
check('visible account pane is focused instead of duplicated', refocus.groups.length === after.groups.length
|
||||
&& refocus.groups.flatMap(g => g.tabs).length === after.groups.flatMap(g => g.tabs).length, `active=${refocus.active} visible=${visibleDefault}`);
|
||||
|
||||
// The menu starts on the focused pane's account, not simply the first row.
|
||||
await page.click('.bt-profile-switcher');
|
||||
await page.click('.bt-acct-row[data-profile="pwork1"] .bt-acct-main', { modifiers: ['Shift'] });
|
||||
await page.waitForTimeout(250);
|
||||
await page.click('.bt-profile-switcher');
|
||||
await page.waitForTimeout(150);
|
||||
const startRow = await page.evaluate(() => document.activeElement?.closest('.bt-acct-row')?.getAttribute('data-profile'));
|
||||
const focusedRow = await page.evaluate(() => document.querySelector('.bt-acct-row.is-focused')?.getAttribute('data-profile'));
|
||||
check('menu opens on the focused pane’s account (not the first row)', startRow === 'pwork1' && focusedRow === 'pwork1', `${startRow} / ${focusedRow}`);
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
// Keyboard: open with Enter, arrows move, Enter picks, Escape closes.
|
||||
await page.focus('.bt-profile-switcher');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForSelector('.bt-acct-menu');
|
||||
const focused0 = await page.evaluate(() => document.activeElement?.closest('.bt-acct-row')?.getAttribute('data-profile'));
|
||||
await page.keyboard.press('ArrowDown');
|
||||
const focused1 = await page.evaluate(() => document.activeElement?.closest('.bt-acct-row')?.getAttribute('data-profile'));
|
||||
check('menu opens with focus on an account and arrows move it', !!focused0 && !!focused1 && focused0 !== focused1, `${focused0} → ${focused1}`);
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await page.waitForTimeout(100);
|
||||
check('ArrowRight opens the row’s actions', !!(await page.$('.bt-acct-sub')));
|
||||
await shot(page, 'row-actions');
|
||||
await page.keyboard.press('Escape');
|
||||
check('Escape closes the row’s actions first', !(await page.$('.bt-acct-sub')) && !!(await page.$('.bt-acct-menu')));
|
||||
await page.keyboard.press('Escape');
|
||||
check('Escape closes the menu', !(await page.$('.bt-acct-menu')));
|
||||
|
||||
// Shift+click forces a new tab (in the focused group) even when a pane is visible.
|
||||
const beforeShift = await layout(page);
|
||||
await page.click('.bt-profile-switcher');
|
||||
await page.click('.bt-acct-row[data-profile="default"] .bt-acct-main', { modifiers: ['Shift'] });
|
||||
await page.waitForTimeout(250);
|
||||
const afterShift = await layout(page);
|
||||
check('Shift+click opens a new tab', afterShift.groups.flatMap(g => g.tabs).length === beforeShift.groups.flatMap(g => g.tabs).length + 1
|
||||
&& afterShift.groups.length === beforeShift.groups.length);
|
||||
|
||||
// Use for new tabs from the row menu.
|
||||
await page.click('.bt-profile-switcher');
|
||||
await page.click('.bt-acct-row[data-profile="pwork1"] .bt-acct-more');
|
||||
await page.click('.bt-acct-sub [data-action="default"]');
|
||||
await page.waitForTimeout(150);
|
||||
const active = await page.evaluate(async () => (await window.__TAURI_INTERNALS__.invoke('profiles_get')).active);
|
||||
check('"Use for new tabs" sets the default account', active === 'pwork1', active);
|
||||
check('the menu stays open to show the new default', !!(await page.$('.bt-acct-row[data-profile="pwork1"] .bt-acct-badge')));
|
||||
await page.waitForTimeout(250);
|
||||
await shot(page, 'menu-after-flow');
|
||||
// The harness refuses like native code while a profile still has a synced tab.
|
||||
const refused = await page.evaluate(async () => {
|
||||
const current = await window.__TAURI_INTERNALS__.invoke('profiles_get');
|
||||
try { await window.__TAURI_INTERNALS__.invoke('profiles_set', { settings: { ...current, profiles: current.profiles.filter(p => p.id !== 'pmemes1') } }); return false; }
|
||||
catch { return true; }
|
||||
});
|
||||
check('removing an account with open tabs directly is refused (harness mirrors native)', refused);
|
||||
// Remove asks first.
|
||||
await page.click('.bt-acct-row[data-profile="pmemes1"] .bt-acct-more');
|
||||
await page.click('.bt-acct-sub [data-action="remove"]');
|
||||
check('Remove asks for confirmation', !!(await page.$('.bt-acct-confirm')));
|
||||
await shot(page, 'remove-confirm');
|
||||
await page.keyboard.press('Escape');
|
||||
check('Cancel keeps the account', !(await page.$('.bt-acct-confirm')) && !!(await page.$('.bt-acct-row[data-profile="pmemes1"]')));
|
||||
await page.click('.bt-acct-row[data-profile="pmemes1"] .bt-acct-more');
|
||||
await page.click('.bt-acct-sub [data-action="remove"]');
|
||||
await page.click('.bt-acct-confirm .bt-acct-remove');
|
||||
await page.waitForTimeout(250);
|
||||
const left = await page.evaluate(async () => (await window.__TAURI_INTERNALS__.invoke('profiles_get')).profiles.map(p => p.id));
|
||||
const openProfiles = await page.evaluate(() => window.__btShellLayout().groups.flatMap(g => g.tabs));
|
||||
check('Remove closes the account’s tabs, then removes it', !left.includes('pmemes1') && !openProfiles.includes('tab2'), `${left} / ${openProfiles}`);
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(150);
|
||||
await shot(page, 'panes-after-flow');
|
||||
}
|
||||
await page.close();
|
||||
|
||||
// Single-account titlebar.
|
||||
page = await open('tabs=4&profiles=1&open=accounts');
|
||||
await page.waitForTimeout(300);
|
||||
await shot(page, 'single-account');
|
||||
await page.close();
|
||||
|
||||
await browser.close();
|
||||
if (failures.length) { console.error(`\n${failures.length} failure(s):\n${failures.join('\n')}`); process.exit(1); }
|
||||
console.log(`\nScreenshots in ${out}`);
|
||||
Loading…
Add table
Add a link
Reference in a new issue