@ultimat3/testing 22.2.1 → 22.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/testing",
3
- "version": "22.2.1",
3
+ "version": "22.3.0",
4
4
  "description": "Test harness: cloned template DBs per worker, frozen clock, sealed network, 6 test types",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,16 +35,16 @@
35
35
  "test": "bun test"
36
36
  },
37
37
  "dependencies": {
38
- "@ultimat3/cache": "22.2.1",
39
- "@ultimat3/core": "22.2.1",
40
- "@ultimat3/db": "22.2.1",
41
- "@ultimat3/entity": "22.2.1",
42
- "@ultimat3/i18n": "22.2.1",
43
- "@ultimat3/jobs": "22.2.1",
44
- "@ultimat3/mail": "22.2.1",
45
- "@ultimat3/policy": "22.2.1",
46
- "@ultimat3/query": "22.2.1",
47
- "@ultimat3/realtime": "22.2.1",
48
- "@ultimat3/time": "22.2.1"
38
+ "@ultimat3/cache": "22.3.0",
39
+ "@ultimat3/core": "22.3.0",
40
+ "@ultimat3/db": "22.3.0",
41
+ "@ultimat3/entity": "22.3.0",
42
+ "@ultimat3/i18n": "22.3.0",
43
+ "@ultimat3/jobs": "22.3.0",
44
+ "@ultimat3/mail": "22.3.0",
45
+ "@ultimat3/policy": "22.3.0",
46
+ "@ultimat3/query": "22.3.0",
47
+ "@ultimat3/realtime": "22.3.0",
48
+ "@ultimat3/time": "22.3.0"
49
49
  }
50
50
  }
@@ -34,6 +34,8 @@ export interface E2eBrowser {
34
34
  export interface OpenE2eBrowserOptions {
35
35
  readonly env?: Readonly<Record<string, string | undefined>> | undefined;
36
36
  readonly timeoutMs?: number | undefined;
37
+ /** Pinned as `Accept-Language` on every request (`cdp-e2e-session.ts`). Absent: Chrome's own. */
38
+ readonly acceptLanguage?: string | undefined;
37
39
  }
38
40
 
39
41
  /**
@@ -63,7 +65,7 @@ export async function openE2eBrowser(options: OpenE2eBrowserOptions = {}): Promi
63
65
  const timeoutMs = budget(options);
64
66
  const executable = await findChrome(options.env ?? process.env);
65
67
  if (executable === undefined) throw new CdpBrowserMissingError({ tried: CHROME_CANDIDATES });
66
- return openLaunched(executable, timeoutMs);
68
+ return openLaunched(executable, timeoutMs, options.acceptLanguage);
67
69
  }
68
70
 
69
71
  /** `undefined` when this machine has no browser. Every other failure still throws. */
@@ -73,7 +75,7 @@ export async function openE2eBrowserIfAvailable(
73
75
  const timeoutMs = budget(options);
74
76
  const executable = await findChrome(options.env ?? process.env);
75
77
  if (executable === undefined) return undefined;
76
- return openLaunched(executable, timeoutMs);
78
+ return openLaunched(executable, timeoutMs, options.acceptLanguage);
77
79
  }
78
80
 
79
81
  /**
@@ -81,11 +83,19 @@ export async function openE2eBrowserIfAvailable(
81
83
  * launched and then refused the CDP handshake would otherwise be left running, holding its profile
82
84
  * directory, for the rest of the test process — one leaked browser per failing suite.
83
85
  */
84
- async function openLaunched(executable: string, timeoutMs: number): Promise<E2eBrowser> {
86
+ async function openLaunched(
87
+ executable: string,
88
+ timeoutMs: number,
89
+ acceptLanguage: string | undefined,
90
+ ): Promise<E2eBrowser> {
85
91
  const launched = await launchChrome({ executable, timeoutMs });
86
92
  const { connection } = launched;
87
93
  try {
88
- const session = await cdpE2eSession({ connection, loadTimeoutMs: timeoutMs });
94
+ const session = await cdpE2eSession({
95
+ connection,
96
+ loadTimeoutMs: timeoutMs,
97
+ ...(acceptLanguage === undefined ? {} : { acceptLanguage }),
98
+ });
89
99
  return compose(launched, session, await session.newTab());
90
100
  } catch (error) {
91
101
  launched.close();
@@ -27,6 +27,12 @@ export interface CdpE2eSessionOptions {
27
27
  readonly connection: CdpConnection;
28
28
  /** The load budget every tab's `goto` waits on, and how long a new tab may take to attach. */
29
29
  readonly loadTimeoutMs: number;
30
+ /**
31
+ * Sent as `Accept-Language` by every page and worker. The e2e step pins the app's default
32
+ * locale here, so a `site/` page negotiates the same language on every box — never the one the
33
+ * CI runner's Chrome was installed with.
34
+ */
35
+ readonly acceptLanguage?: string | undefined;
30
36
  }
31
37
 
32
38
  const POLL_MS = 50;
@@ -90,6 +96,15 @@ export async function cdpE2eSession(options: CdpE2eSessionOptions): Promise<E2eS
90
96
  // Network domain is off — an `offline()` that does nothing while the assertion after it
91
97
  // reads as proof.
92
98
  const configured: Promise<unknown>[] = [send('Network.enable', {}, session)];
99
+ if (options.acceptLanguage !== undefined) {
100
+ configured.push(
101
+ send(
102
+ 'Network.setExtraHTTPHeaders',
103
+ { headers: { 'accept-language': options.acceptLanguage } },
104
+ session,
105
+ ),
106
+ );
107
+ }
93
108
  if (cut) configured.push(condition(session));
94
109
  if (type === 'page') {
95
110
  configured.push(send('Page.enable', {}, session), send('Runtime.enable', {}, session));
@@ -0,0 +1,30 @@
1
+ // The locale an e2e run's browser asks for: the app's own `defaultLocale`, read off `app.config.ts`
2
+ // the way the CLI reads its other boot facts. `@ultimat3/testing` sits below `@ultimat3/cli`, so the
3
+ // file name and the export name are restated here rather than imported from it.
4
+
5
+ import { join } from 'node:path'; // why: Bun ships no path join.
6
+
7
+ /** The app root's config module and the export holding the config — `x new` writes both. */
8
+ const APP_CONFIG_FILE = 'app.config.ts';
9
+ const APP_CONFIG_EXPORT = 'config';
10
+
11
+ /**
12
+ * The app's `defaultLocale`, or `undefined` when it cannot be read — and then nothing is pinned.
13
+ * Never a guessed `en`: pinning the wrong language would make every run photograph and assert on a
14
+ * page no visitor at the unprefixed URL sees, which is worse than the runner's own Chrome locale.
15
+ * The import runs the config module, which may validate an environment this process lacks.
16
+ */
17
+ export async function e2eDefaultLocale(root: string): Promise<string | undefined> {
18
+ const path = join(root, APP_CONFIG_FILE);
19
+ if (!(await Bun.file(path).exists())) return undefined;
20
+ let module: Record<string, unknown>;
21
+ try {
22
+ module = (await import(path)) as Record<string, unknown>;
23
+ } catch {
24
+ return undefined;
25
+ }
26
+ const config = Object.hasOwn(module, APP_CONFIG_EXPORT) ? module[APP_CONFIG_EXPORT] : undefined;
27
+ if (typeof config !== 'object' || config === null) return undefined;
28
+ const locale = (config as Record<string, unknown>)['defaultLocale'];
29
+ return typeof locale === 'string' && locale !== '' ? locale : undefined;
30
+ }
@@ -7,14 +7,18 @@ import { openE2eBrowser } from './cdp-browser';
7
7
  import { startE2eApp } from './e2e-app';
8
8
  import { E2E_ROOT_ENV } from './e2e-browser-handle';
9
9
  import { installE2eDriver } from './e2e-driver';
10
+ import { e2eDefaultLocale } from './e2e-locale';
10
11
  import { startE2eRun } from './e2e-run';
11
12
 
12
13
  const root = Bun.env[E2E_ROOT_ENV];
13
14
  if (root !== undefined && root !== '') {
15
+ // Read once, before the app boots: every browser this run opens — the first and every relaunch —
16
+ // asks for the app's default locale, so a page's language never depends on the runner's Chrome.
17
+ const acceptLanguage = await e2eDefaultLocale(root);
14
18
  await startE2eRun({
15
19
  app: await startE2eApp({ root }),
16
20
  // `openE2eBrowser`, never the `IfAvailable` door: the step only names a root after finding one.
17
- openBrowser: openE2eBrowser,
21
+ openBrowser: () => openE2eBrowser(acceptLanguage === undefined ? {} : { acceptLanguage }),
18
22
  install: installE2eDriver,
19
23
  beforeEach,
20
24
  afterAll,