wayBetter for X

This commit is contained in:
CRTkafa 2026-10-01 06:01:13 +07:00
commit 2f1e35ba20
720 changed files with 147465 additions and 0 deletions

View 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|Fishies|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}`);