Files
RockChinQandClaude Opus 5.5 624d8efd97 test(web): align frontend checks with the plan comparison dialog
- Use LangBot Account wording in the Japanese checkout handoff copy.
- Assert the Workspace Settings plan entry opens CloudPlansDialog.
- Format revision tests with Prettier.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-17 01:54:05 +08:00

226 lines
7.3 KiB
JavaScript

import { createServer } from 'vite';
import { chromium } from '@playwright/test';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { mkdir, writeFile } from 'node:fs/promises';
const workdir = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'../../../..',
);
const evidence = path.join(workdir, 'evidence');
await mkdir(evidence, { recursive: true });
const servers = [];
let browser;
const results = [];
const latest = (mode) => ({
uuid: 'latest',
name: 'pro',
revision: mode === 'same' ? 1 : 2,
display_name: { en_US: 'Pro' },
description: { en_US: 'Workspace plan' },
price_cny: '70',
price_usd: '10',
price_jpy: '1500',
features: [],
limits:
mode === 'equal' || mode === 'same'
? { 'bots.max': 2, 'members.max': 3 }
: { 'bots.max': 8, 'members.max': 5 },
entitlement_features: { mcp_stdio: !['same', 'equal'].includes(mode) },
disabled_adapters: ['same', 'equal'].includes(mode) ? ['telegram'] : [],
chat_history_retention_days: ['same', 'equal'].includes(mode) ? 30 : 90,
monthly_bonus_credits: ['same', 'equal'].includes(mode) ? 10 : 20,
is_active: true,
subscription_disabled: mode === 'paused',
sort_order: 1,
highlight: false,
});
try {
for (const [repo] of [
['space', 4185],
['core', 4186],
]) {
const server = await createServer({
configFile: path.join(
workdir,
repo,
'web/tests/revisions/vite.config.mjs',
),
});
await server.listen();
servers.push(server);
}
browser = await chromium.launch({ headless: true });
for (const repo of ['space', 'core']) {
const modes =
repo === 'space'
? [
'changed',
'same',
'equal',
'paused',
'removed',
'member',
'stripe',
'cancel-failure',
]
: [
'changed',
'same',
'equal',
'paused',
'removed',
'member',
'unknown',
'error',
];
for (const mode of modes) {
const page = await browser.newPage({
viewport: { width: 1360, height: 1100 },
});
page.setDefaultTimeout(120000);
const errors = [];
page.on('pageerror', (error) => {
errors.push(error.message);
console.error(error.message);
});
await page.route('**/*', async (route) => {
const url = new URL(route.request().url());
if (url.hostname === '127.0.0.1') return route.continue();
if (url.pathname === '/api/v1/cloud/v2/plans')
return route.fulfill({
status: mode === 'error' ? 500 : 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: mode === 'removed' ? [] : [latest(mode)],
}),
});
return route.abort();
});
const port = repo === 'space' ? 4185 : 4186;
await page.goto(
`http://127.0.0.1:${port}/tests/revisions/index.html?mode=${mode}${mode === 'stripe' ? '&intent=1' : ''}`,
{ waitUntil: 'domcontentloaded', timeout: 120000 },
);
if (repo === 'core' && mode === 'error')
await page.getByRole('alert').waitFor();
else if (mode === 'unknown')
await page
.getByText(/Current subscription details are unavailable/)
.waitFor();
else
await page
.locator(
repo === 'space'
? '[data-plan-comparison]'
: '[data-cloud-plans] article',
)
.first()
.waitFor();
if (errors.length)
throw new Error(`${repo}/${mode}: ${errors.join(';')}`);
if (await page.locator('[data-revision-comparison]').count())
throw new Error('Standalone comparison remains');
if (
['same', 'equal'].includes(mode) &&
(await page.locator('[data-owned-plan]').count())
)
throw new Error('Identical benefits duplicated');
const area = page.locator(
repo === 'space' ? '[data-plan-comparison]' : '[data-cloud-plans]',
);
if (!['unknown', 'error'].includes(mode)) {
const copy = await area.innerText();
if (
/&#|revision|mcp stdio|workspace multi user|platform telegram/.test(
copy,
)
)
throw new Error('Internal metadata leaked into plan presentation');
if (
repo === 'core' &&
(await area.locator('[data-changed], .bg-amber-500\\/10').count())
)
throw new Error('Diff highlighting remains');
if (
['same', 'equal'].includes(mode) &&
/Subscribed on|Latest quotas for this plan/.test(copy)
)
throw new Error('Unnecessary comparison labels');
}
if (mode === 'changed') {
await area.getByText(/Subscribed on/).waitFor();
await area
.getByText('Latest quotas for this plan', { exact: true })
.waitFor();
const owned = page.locator('[data-owned-plan]');
if ((await owned.count()) !== 1)
throw new Error('Missing integrated owned version');
const horizontal = await owned.evaluate((el) => {
const next = el.nextElementSibling;
return (
!!next &&
Math.abs(
next.getBoundingClientRect().y - el.getBoundingClientRect().y,
) < 5 &&
next.getBoundingClientRect().x > el.getBoundingClientRect().x
);
});
if (!horizontal)
throw new Error('Owned and latest are not adjacent horizontally');
}
if (repo === 'space' && mode === 'stripe') {
if (await page.getByRole('dialog').count())
throw new Error('Deep link bypassed renewal gate');
await page
.getByRole('button', { name: 'Manage subscription' })
.first()
.click();
await page.getByText('Server refresh confirmed cancellation').waitFor();
if (!(await page.getByRole('dialog').count()))
await page
.getByRole('button', { name: /Upgrade to|Subscribe to|Choose/ })
.last()
.click();
await page.getByRole('dialog').waitFor();
}
if (repo === 'space' && mode === 'cancel-failure') {
await page
.getByRole('button', { name: 'Manage subscription' })
.first()
.click();
await page.getByText('Cancellation failed').waitFor();
if (await page.getByRole('dialog').count())
throw new Error('Failed cancellation opened checkout');
}
if (
mode === 'member' &&
repo === 'core' &&
(await page.getByRole('link').count())
)
throw new Error('Member purchase link');
if (
mode === 'changed' ||
mode === 'equal' ||
mode === 'member' ||
mode === 'paused'
)
await page.screenshot({
path: path.join(evidence, `${repo}-${mode}.png`),
fullPage: true,
});
results.push(`${repo}/${mode}: PASS`);
console.log(results.at(-1));
await page.close();
}
}
} finally {
await writeFile(
path.join(evidence, 'browser-results.txt'),
results.join('\n') + '\n',
);
if (browser) await browser.close();
for (const server of servers) await server.close();
}