@desplega.ai/qa-use 2.16.0 → 2.18.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.
Files changed (48) hide show
  1. package/README.md +3 -0
  2. package/dist/lib/api/browser.d.ts.map +1 -1
  3. package/dist/lib/api/browser.js +6 -1
  4. package/dist/lib/api/browser.js.map +1 -1
  5. package/dist/lib/browser/index.d.ts.map +1 -1
  6. package/dist/lib/browser/index.js +5 -1
  7. package/dist/lib/browser/index.js.map +1 -1
  8. package/dist/lib/env/force-headless.d.ts +36 -0
  9. package/dist/lib/env/force-headless.d.ts.map +1 -0
  10. package/dist/lib/env/force-headless.js +56 -0
  11. package/dist/lib/env/force-headless.js.map +1 -0
  12. package/dist/package.json +1 -1
  13. package/dist/src/cli/commands/browser/_detached.d.ts.map +1 -1
  14. package/dist/src/cli/commands/browser/_detached.js +8 -1
  15. package/dist/src/cli/commands/browser/_detached.js.map +1 -1
  16. package/dist/src/cli/commands/browser/create.d.ts.map +1 -1
  17. package/dist/src/cli/commands/browser/create.js +16 -1
  18. package/dist/src/cli/commands/browser/create.js.map +1 -1
  19. package/dist/src/cli/commands/browser/run.d.ts.map +1 -1
  20. package/dist/src/cli/commands/browser/run.js +9 -0
  21. package/dist/src/cli/commands/browser/run.js.map +1 -1
  22. package/dist/src/cli/commands/test/export.js +3 -3
  23. package/dist/src/cli/commands/test/export.js.map +1 -1
  24. package/dist/src/cli/commands/test/list.d.ts.map +1 -1
  25. package/dist/src/cli/commands/test/list.js +4 -2
  26. package/dist/src/cli/commands/test/list.js.map +1 -1
  27. package/dist/src/cli/commands/test/run.d.ts.map +1 -1
  28. package/dist/src/cli/commands/test/run.js +13 -0
  29. package/dist/src/cli/commands/test/run.js.map +1 -1
  30. package/dist/src/cli/commands/test/sync.d.ts +60 -0
  31. package/dist/src/cli/commands/test/sync.d.ts.map +1 -1
  32. package/dist/src/cli/commands/test/sync.js +149 -26
  33. package/dist/src/cli/commands/test/sync.js.map +1 -1
  34. package/dist/src/cli/lib/browser.d.ts.map +1 -1
  35. package/dist/src/cli/lib/browser.js +5 -2
  36. package/dist/src/cli/lib/browser.js.map +1 -1
  37. package/dist/src/cli/lib/config.d.ts.map +1 -1
  38. package/dist/src/cli/lib/config.js +8 -0
  39. package/dist/src/cli/lib/config.js.map +1 -1
  40. package/dist/src/utils/strings.d.ts +21 -0
  41. package/dist/src/utils/strings.d.ts.map +1 -1
  42. package/dist/src/utils/strings.js +29 -0
  43. package/dist/src/utils/strings.js.map +1 -1
  44. package/lib/api/browser.ts +9 -1
  45. package/lib/browser/index.ts +9 -1
  46. package/lib/env/force-headless.test.ts +83 -0
  47. package/lib/env/force-headless.ts +61 -0
  48. package/package.json +1 -1
@@ -0,0 +1,83 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2
+ import { assertHeadlessAllowed, isForceHeadless, resolveForcedHeadless } from './force-headless.js';
3
+ import { clearConfigCache } from './index.js';
4
+
5
+ const ENV_KEY = 'QA_USE_FORCE_HEADLESS';
6
+
7
+ let saved: string | undefined;
8
+
9
+ beforeEach(() => {
10
+ saved = process.env[ENV_KEY];
11
+ delete process.env[ENV_KEY];
12
+ clearConfigCache();
13
+ });
14
+
15
+ afterEach(() => {
16
+ if (saved === undefined) {
17
+ delete process.env[ENV_KEY];
18
+ } else {
19
+ process.env[ENV_KEY] = saved;
20
+ }
21
+ clearConfigCache();
22
+ });
23
+
24
+ describe('isForceHeadless', () => {
25
+ test('false when unset', () => {
26
+ expect(isForceHeadless()).toBe(false);
27
+ });
28
+
29
+ test('true for truthy values (case/whitespace insensitive)', () => {
30
+ for (const v of ['1', 'true', 'TRUE', ' yes ', 'on', 'On']) {
31
+ process.env[ENV_KEY] = v;
32
+ expect(isForceHeadless()).toBe(true);
33
+ }
34
+ });
35
+
36
+ test('false for falsy / empty values', () => {
37
+ for (const v of ['0', 'false', 'no', 'off', '']) {
38
+ process.env[ENV_KEY] = v;
39
+ expect(isForceHeadless()).toBe(false);
40
+ }
41
+ });
42
+ });
43
+
44
+ describe('assertHeadlessAllowed', () => {
45
+ test('no-op when force is off', () => {
46
+ expect(() => assertHeadlessAllowed(false, '--no-headless flag')).not.toThrow();
47
+ });
48
+
49
+ test('no-op when requested is true or undefined', () => {
50
+ process.env[ENV_KEY] = '1';
51
+ expect(() => assertHeadlessAllowed(true, 'src')).not.toThrow();
52
+ expect(() => assertHeadlessAllowed(undefined, 'src')).not.toThrow();
53
+ });
54
+
55
+ test('throws when force is on and requested is false', () => {
56
+ process.env[ENV_KEY] = '1';
57
+ expect(() => assertHeadlessAllowed(false, '--no-headless flag')).toThrow(
58
+ /QA_USE_FORCE_HEADLESS/
59
+ );
60
+ expect(() => assertHeadlessAllowed(false, '--no-headless flag')).toThrow(/--no-headless flag/);
61
+ });
62
+ });
63
+
64
+ describe('resolveForcedHeadless', () => {
65
+ test('passthrough when force is off', () => {
66
+ expect(resolveForcedHeadless(false)).toBe(false);
67
+ expect(resolveForcedHeadless(true)).toBe(true);
68
+ expect(resolveForcedHeadless(undefined)).toBeUndefined();
69
+ });
70
+
71
+ test('coerces undefined to true when force is on', () => {
72
+ process.env[ENV_KEY] = 'true';
73
+ expect(resolveForcedHeadless(undefined)).toBe(true);
74
+ expect(resolveForcedHeadless(true)).toBe(true);
75
+ });
76
+
77
+ test('throws on explicit false when force is on', () => {
78
+ process.env[ENV_KEY] = '1';
79
+ expect(() => resolveForcedHeadless(false, 'startBrowser options')).toThrow(
80
+ /startBrowser options/
81
+ );
82
+ });
83
+ });
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Operator-level "force headless" override.
3
+ *
4
+ * When `QA_USE_FORCE_HEADLESS` is set to a truthy value, qa-use refuses
5
+ * to launch a visible (headful) browser. Any explicit headful request
6
+ * — `--no-headless`, `--headful`, `defaults.headless: false`, or an MCP
7
+ * client passing `headless: false` — fails with a clear error.
8
+ *
9
+ * This is a hard policy switch for environments that must guarantee no
10
+ * visible browser ever opens (CI, locked-down hosts, security policy).
11
+ *
12
+ * The env var is read via `getEnv` so it also resolves through the
13
+ * `~/.qa-use.json` env block, consistent with other `QA_USE_*` vars.
14
+ */
15
+
16
+ import { getEnv } from './index.js';
17
+
18
+ const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
19
+
20
+ /**
21
+ * Returns `true` when force-headless is active.
22
+ */
23
+ export function isForceHeadless(): boolean {
24
+ const raw = getEnv('QA_USE_FORCE_HEADLESS');
25
+ if (!raw) return false;
26
+ return TRUTHY.has(raw.trim().toLowerCase());
27
+ }
28
+
29
+ /**
30
+ * Throw if force-headless is on AND the caller explicitly asked for
31
+ * headful (`requested === false`). `undefined` and `true` pass through.
32
+ *
33
+ * `source` is interpolated into the error so the user can see which
34
+ * input triggered it (e.g. `"--no-headless flag"`).
35
+ */
36
+ export function assertHeadlessAllowed(requested: boolean | undefined, source: string): void {
37
+ if (requested !== false) return;
38
+ if (!isForceHeadless()) return;
39
+ throw new Error(
40
+ `Headful browser mode is disabled by QA_USE_FORCE_HEADLESS.\n` +
41
+ `Triggered by: ${source}.\n` +
42
+ `Unset the env var or remove the headful request to proceed.`
43
+ );
44
+ }
45
+
46
+ /**
47
+ * Resolve the effective headless value at a launch site.
48
+ *
49
+ * - Asserts that an explicit `false` is not silently overridden.
50
+ * - When force-headless is on, coerces `undefined` to `true`.
51
+ * - Otherwise returns the requested value unchanged (callers keep
52
+ * their existing `?? true` defaulting downstream).
53
+ */
54
+ export function resolveForcedHeadless(
55
+ requested: boolean | undefined,
56
+ source = 'browser launch options'
57
+ ): boolean | undefined {
58
+ assertHeadlessAllowed(requested, source);
59
+ if (isForceHeadless()) return true;
60
+ return requested;
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@desplega.ai/qa-use",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "packageManager": "bun@^1.3.4",
5
5
  "description": "QA automation tool for browser testing with MCP server support",
6
6
  "type": "module",