mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-10-21 14:21:48 +00:00
chore: restore regression coverage after WebUI retirement (#11101)
* chore(web-shell): follow up #9812 review findings and deferred suggestions Address the remaining review suggestions from the @qwen-code/webui retirement (#9812) and the deferred items tracked in #11076: - Fix the 01-architecture.md adapter diagram path (missing session/). - Correct the followup-suggestions doc: generation fires on end_turn, and headless/SDK daemon clients should opt out to avoid the per-turn LLM cost. - Pin playwright to 1.61.1 to restore version parity with @playwright/test. - Restore file: scheme rejection coverage in Markdown.test.ts. - Pin the historical sessionStorage key prefix with a literal assertion. - Cover the legacy exported JSONL rejection and ChatRecord happy path. - Restore the shell-substring classifier trap with a live path. - Point useDaemonFollowupSuggestion docs at ChatEditor, not the deleted InputForm. - Replace the theme-toggle timing-race assertion and exercise the unhandledrejection fail-closed path in the document browser gate. Closes #11076 * fix(test): poll the theme class instead of a matcher vitest lacks The browser gate imports expect from vitest and drives playwright's Locator directly, so toHaveClass does not exist on the assertion and typecheck:integration failed with TS2339 on this line. Poll the class attribute through the same expect.poll idiom the neighbouring assertions use, which keeps the retry the assertion was switched to and needs no @playwright/test dependency. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-conflict/jmtooas8sln * fix(test): assert the exported theme class exactly `document-main.tsx` toggles `dark` and `light` on <html> mutually exclusively and nothing else writes `documentElement` classes, so the attribute is always exactly one of them. Matching `/light/` as an unanchored substring would also accept `dark light`, which is the theme-toggle breakage this poll exists to catch. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtow5pgslz * fix(test): realpath the exporter main check and pin its JSONL guard Node realpath-resolves `import.meta.url` for the ESM main module but leaves `process.argv[1]` as invoked, so comparing the raw spellings made a symlinked invocation skip `main()` and exit 0 having written nothing — a silent no-op the harness reads as success. Compare realpaths instead and export the predicate so both directions are pinned. Also extract the input gate into `assertRenderableJsonl` so the legacy exported-JSONL rejection can be asserted directly: the previous tests only pinned the two predicates feeding it, so deleting the guard and losing the remediation hint kept every test green. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtow5pgslz * docs(followup): name the real ACP stop reasons, drop "unconditional" The parenthetical this PR added named `interrupted` as a daemon stop reason. The ACP `StopReason` union is `end_turn | max_tokens | max_turn_requests | refusal | cancelled`, so there is no `interrupted`; an interrupted turn reports `cancelled`. Name the four suppressing values the union actually defines so an integrator writing `stopReason === '...'` gets a branch that can fire. "unconditional" was wider than the code and contradicted the condition list on the same page: `Session.ts#maybeEmitFollowupSuggestion` returns early on the stop reason, the todo stop guard, `ui.enableFollowupSuggestions`, PLAN approval mode, a missing chat, and a non-model last history entry. Point at those conditions instead; the cost advice still holds because the `enableFollowupSuggestions === false` return sits before `generatePromptSuggestion` is reached. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtp6vibomd * test(web-shell): drop the duplicate historical-key assertion `persists under the literal historical key prefix`, added on this branch, became a byte-equivalent twin of `writes under the historical WebUI key`, which reached main with #11107 and arrived here through the merge at this PR's head: same `persistStableClientId('client-a', 'session-a')`, same literal `qwen-code-webui-client-id:session:session-a`, same expectation. One fact was reported under two names, so a prefix rename reddened two tests and an auditor could not tell which copy was load-bearing. The surviving test's comment already carries the rename rationale, so dropping the copy loses no coverage. Verified with `cd packages/web-shell && npx vitest run client/daemon/session/clientLifecycle.test.ts`: 19 passed before, 18 passed after. Mutating SESSION_CLIENT_ID_STORAGE_PREFIX to `qwen-code-webshell-client-id:session:` after the dedupe still reddens 3 tests (15 passed), including the surviving `writes under the historical WebUI key`; reverting the mutation returns to 18 passed. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtp6vibomd * test(integration): drop the unreachable unhandledrejection arm The block this PR added after the CDN-failure loop could not reach the `window.addEventListener('unhandledrejection', showLoadError)` handler its comment claimed to exercise. `page.route('**/*', route => route.abort('blockedbyclient'))` fails the `<script id="transcript-renderer">` load, the template's capture-phase `error` listener (document-index.html:45-56, matching `event.target.id === 'transcript-renderer'`) calls `showLoadError()` first, and `setContent(..., { waitUntil: 'load' })` only resolves after that subresource has failed - so `data-render-complete='error'` and the alert text were already in place before `page.evaluate` dispatched anything, and showLoadError's one-shot guard (`if (document.body.dataset.renderComplete) return;`, document-index.html:30) made the synthetic rejection a no-op. The block was a strict duplicate of the loop's own `rendererBody === null` iteration: identical all-abort route, identical two assertions. Exercising the handler for real needs a renderer body that passes the document's pinned SRI integrity and then rejects during boot; no body this gate can serve passes SRI, so the arm cannot be made honest here. Deleting it returns the file to origin/main's state for this test - the handler and its missing coverage are both pre-existing on main (packages/web-templates is not in this PR's diff), so no coverage this PR was responsible for is lost. Verified: `npm run typecheck:integration` reports 0 errors in this file (6 remain, all in packages/** against generated/dist paths absent from an unbuilt worktree: channels/feishu, cli/src/generated/git-commit.js, web-templates/src/generated/*); `npx prettier --check` and `npx eslint` clean on the file. The browser gate itself was not executed locally - it needs the full build chain plus Chromium. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtpfg5ewmp * ci(review-context): route concurrent-runner changes to the helper-tests lane This PR added `integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.test.mjs`, the first `node:test` file under `integration-tests/`, and wired it into CI through the `HELPER_TESTS` env list in `.github/workflows/ci.yml`. The tracked review-context manifest still mapped the whole `integration-tests/**` path to `recommendedTests: ['integration-tests']`, whose vitest lane collects `**/*.test.ts` only (`integration-tests/vitest.config.ts:21`), so a future PR touching only the exporter and its `.mjs` test is told to run a lane that cannot collect the file - the same silent drop `HELPER_TESTS` exists to prevent. Add a narrower rule for `integration-tests/concurrent-runner/**` recommending `helper-tests`. Matching rules merge rather than first-match, so the lane list for that directory becomes the union and every other `integration-tests/` path is untouched. The pinned `expectedManifest` in `manifest-repository-context.committed.test.ts` moves with it, which is what that pin is for. Verified through the real provider (tsx importing `packages/cli/src/commands/review/lib/manifest-repository-context.ts`) because the packages/cli vitest globalSetup gate refuses to run on this unbuilt worktree: concurrent-runner .mjs -> ["helper-tests","integration-tests"] concurrent-runner .js -> ["helper-tests","integration-tests"] chat-transcript-document.test.ts -> ["integration-tests"] (unchanged) all 10 rules co-matching -> non-null, 80 relatedPaths, and every `paths` probe matched some rule Also checked the committed manifest deep-equals the test's pinned literal (10 rules on both sides), and that dropping the new rule breaks that equality, so the pin is not tautological. `npx prettier --check` and `npx eslint` clean on both files. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Patrol-Run: qwen-pr-closeout/jmtpfg5ewmp * fix(deps): sync pnpm lockfile for Playwright pin --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
dca2ddd317
commit
32f51a330b
12 changed files with 156 additions and 64 deletions
|
|
@ -124,6 +124,7 @@ test('platform-coupled subsystems match on segments, not substrings', () => {
|
|||
'packages/core/src/telemetry/uploader.ts',
|
||||
'packages/cli/src/services/plateauDetector.ts',
|
||||
'packages/web-shell/client/components/Shellfish.tsx',
|
||||
'packages/cli/src/ui/hooks/shellCommandProcessor.ts',
|
||||
]) {
|
||||
assert.equal(classifyChangedFiles([file]), PLATFORM_INSENSITIVE, file);
|
||||
}
|
||||
|
|
|
|||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -87,7 +87,7 @@ env:
|
|||
# `full` (classify_pr computes a profile only for pull_request), so what
|
||||
# the fast lane skips is still re-checked post-merge.
|
||||
HELPER_TESTS_DEP_FREE: '.github/scripts/pr-safety-precheck.test.mjs .github/scripts/cap-release-notes.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/ci/classify-platform-sensitivity.test.mjs .github/scripts/ci/classify-pr-profile.test.mjs .github/scripts/upsert-bot-comment.test.mjs .github/scripts/ci/main-failure-signature.test.mjs .github/scripts/dsw-swe-verified/make-manifest.test.mjs .github/scripts/dsw-swe-verified/make-terminal-bench-manifest.test.mjs .github/scripts/update-ecs-runner-qwen-workflow.test.mjs .github/scripts/web-shell-visuals-compose.test.mjs .github/scripts/serve-ab-diff.test.mjs .github/scripts/serve-ab-drive.test.mjs .github/scripts/autofix-status-heartbeat.test.mjs .github/scripts/check-disk-floor.test.mjs'
|
||||
HELPER_TESTS: '.github/scripts/pr-safety-precheck.test.mjs .github/scripts/cap-release-notes.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/ci/classify-platform-sensitivity.test.mjs .github/scripts/ci/classify-pr-profile.test.mjs .github/scripts/upsert-bot-comment.test.mjs .github/scripts/ci/main-failure-signature.test.mjs .github/scripts/classify-release-notes.test.mjs .github/scripts/dsw-swe-verified/make-manifest.test.mjs .github/scripts/dsw-swe-verified/make-terminal-bench-manifest.test.mjs .github/scripts/resolve-sandbox-image.test.mjs .github/scripts/update-ecs-runner-qwen-workflow.test.mjs .github/scripts/web-shell-visuals-publish.test.mjs .github/scripts/web-shell-visuals-compose.test.mjs .github/scripts/serve-ab-diff.test.mjs .github/scripts/serve-ab-drive.test.mjs .github/scripts/qwen-triage-workflow.test.mjs .github/scripts/assign-issue-owner.test.mjs .github/scripts/auto-minimize-spam.test.mjs .github/scripts/ci-runner-routing.test.mjs .github/scripts/autofix-status-heartbeat.test.mjs .github/scripts/assign-pr-owner.test.mjs .github/scripts/check-disk-floor.test.mjs .github/scripts/ci-disk-pressure.test.mjs .github/scripts/e2e-build.test.mjs'
|
||||
HELPER_TESTS: '.github/scripts/pr-safety-precheck.test.mjs .github/scripts/cap-release-notes.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/ci/classify-platform-sensitivity.test.mjs .github/scripts/ci/classify-pr-profile.test.mjs .github/scripts/upsert-bot-comment.test.mjs .github/scripts/ci/main-failure-signature.test.mjs .github/scripts/classify-release-notes.test.mjs .github/scripts/dsw-swe-verified/make-manifest.test.mjs .github/scripts/dsw-swe-verified/make-terminal-bench-manifest.test.mjs .github/scripts/resolve-sandbox-image.test.mjs .github/scripts/update-ecs-runner-qwen-workflow.test.mjs .github/scripts/web-shell-visuals-publish.test.mjs .github/scripts/web-shell-visuals-compose.test.mjs .github/scripts/serve-ab-diff.test.mjs .github/scripts/serve-ab-drive.test.mjs .github/scripts/qwen-triage-workflow.test.mjs .github/scripts/assign-issue-owner.test.mjs .github/scripts/auto-minimize-spam.test.mjs .github/scripts/ci-runner-routing.test.mjs .github/scripts/autofix-status-heartbeat.test.mjs .github/scripts/assign-pr-owner.test.mjs .github/scripts/check-disk-floor.test.mjs .github/scripts/ci-disk-pressure.test.mjs .github/scripts/e2e-build.test.mjs integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.test.mjs'
|
||||
# The growth ratchet and its vitest mirror compare each workflow against
|
||||
# the PR's base commit to tell "this PR grew the file" apart from "the
|
||||
# baseline went stale on main" (#9904). Wired once here so every lane
|
||||
|
|
@ -837,7 +837,7 @@ jobs:
|
|||
# OUTCOME — no vitest unit-test run, whose flake was measured failing
|
||||
# 41% of post-merge runs in the week of the split — not lightness: the
|
||||
# bundle-closure check builds and bundles the CLI, the helper-suite
|
||||
# steps execute the 25-file HELPER_TESTS battery (most spawn
|
||||
# steps execute the 26-file HELPER_TESTS battery (most spawn
|
||||
# subprocesses), and the lane runs on the same contended pool as
|
||||
# everything else, which is what the routed 90/45 ceiling below prices.
|
||||
# One honest limit, recorded so the required-check follow-up is argued
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@
|
|||
"recommendedTests": ["integration-tests"],
|
||||
"requiredConfigurations": ["node22"]
|
||||
},
|
||||
{
|
||||
"paths": ["integration-tests/concurrent-runner/**"],
|
||||
"domains": ["integration-tests"],
|
||||
"recommendedTests": ["helper-tests"],
|
||||
"requiredConfigurations": ["node22"]
|
||||
},
|
||||
{
|
||||
"paths": [".github/workflows/**"],
|
||||
"domains": ["ci", "workflows"],
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Qwen Code can predict what you want to type next and show it as placeholder text in the input area. This feature uses an LLM call to analyze the conversation context and generate a natural next step suggestion.
|
||||
|
||||
This feature works end-to-end in both the CLI and Web Shell. Generation is automatic and server-side: after each completed turn the daemon emits the suggestion on the session stream (on by default; set `ui.enableFollowupSuggestions` to `false` to opt out), and Web Shell's composer already wires the `useDaemonFollowupSuggestion` hook, so suggestions render and accept with no additional host wiring.
|
||||
This feature works end-to-end in both the CLI and Web Shell. Generation is automatic and server-side: after each cleanly completed turn (the daemon's `end_turn` stop reason — a `cancelled`, `refusal`, `max_tokens`, or `max_turn_requests` turn gets none) the daemon emits the suggestion on the session stream (on by default; set `ui.enableFollowupSuggestions` to `false` to opt out), and Web Shell's composer already wires the `useDaemonFollowupSuggestion` hook, so suggestions render and accept with no additional host wiring.
|
||||
|
||||
## How It Works
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ Suggestions are generated when all of the following conditions are met:
|
|||
- The approval mode is not set to `plan`
|
||||
- The feature is enabled (on by default — set `ui.enableFollowupSuggestions` to `false` to turn it off)
|
||||
|
||||
Suggestions will not appear in non-interactive mode (e.g., headless/SDK mode).
|
||||
Suggestions will not appear in the CLI's non-interactive mode (e.g., headless/SDK mode). In the daemon, generation is server-side and runs after every turn that meets the conditions above, so a headless or SDK client that cannot render the suggestion should set `ui.enableFollowupSuggestions` to `false` to avoid the per-turn LLM cost.
|
||||
|
||||
Suggestions are automatically dismissed when:
|
||||
|
||||
|
|
|
|||
|
|
@ -469,7 +469,11 @@ describe('ExportTranscriptDocument browser gate', () => {
|
|||
await expandAll.click();
|
||||
expect(await expandAll.isDisabled()).toBe(true);
|
||||
await themeToggle.click();
|
||||
expect(await page.locator('html').getAttribute('class')).toContain('light');
|
||||
// `document-main.tsx` toggles `dark` and `light` on <html> mutually
|
||||
// exclusively, so the attribute is exactly one of them.
|
||||
await expect
|
||||
.poll(() => page.locator('html').getAttribute('class'))
|
||||
.toBe('light');
|
||||
// Since #11091 a document does not render diagrams: mermaid is stubbed out
|
||||
// of this bundle, and the fence degrades to its own source in a plain <pre>
|
||||
// so it stays readable, selectable and findable. Math is deliberately kept,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import fs from 'node:fs';
|
|||
import fsp from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import readline from 'node:readline';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
async function loadExportApi() {
|
||||
try {
|
||||
|
|
@ -184,13 +184,28 @@ async function main() {
|
|||
console.log(`Wrote HTML export to: ${outputPath}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Node realpath-resolves `import.meta.url` for the ESM main module but leaves
|
||||
* `process.argv[1]` as it was invoked, so compare realpaths. Comparing the raw
|
||||
* spellings makes any symlinked invocation skip `main()` and exit 0 having
|
||||
* written nothing.
|
||||
*/
|
||||
export function isMainModule(argv1, metaUrl) {
|
||||
if (typeof argv1 !== 'string') return false;
|
||||
let resolved;
|
||||
try {
|
||||
resolved = fs.realpathSync(argv1);
|
||||
} catch {
|
||||
resolved = path.resolve(argv1);
|
||||
}
|
||||
return metaUrl === pathToFileURL(resolved).href;
|
||||
}
|
||||
|
||||
// Only run when invoked as the CLI. Importing this module (the test does)
|
||||
// must not execute a render or touch process state.
|
||||
const invokedDirectly =
|
||||
typeof process.argv[1] === 'string' &&
|
||||
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
||||
const isMain = isMainModule(process.argv[1], import.meta.url);
|
||||
|
||||
if (invokedDirectly) {
|
||||
if (isMain) {
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
import { isMainModule } from './export-html-from-chatrecord-jsonl.js';
|
||||
|
||||
// The input gate itself is covered by the vitest suite in
|
||||
// scripts/tests/export-html-from-chatrecord-jsonl.test.js; this lane only pins
|
||||
// the main-module check, which that suite cannot exercise without spawning.
|
||||
const exporterPath = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'export-html-from-chatrecord-jsonl.js',
|
||||
);
|
||||
const exporterUrl = pathToFileURL(fs.realpathSync(exporterPath)).href;
|
||||
|
||||
test('recognizes a direct main-module invocation', () => {
|
||||
assert.equal(isMainModule(exporterPath, exporterUrl), true);
|
||||
});
|
||||
|
||||
test('recognizes a main-module invocation through a symlinked path', () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'exporter-is-main-'));
|
||||
try {
|
||||
const link = path.join(dir, 'exporter-link.js');
|
||||
fs.symlinkSync(exporterPath, link);
|
||||
|
||||
assert.equal(isMainModule(link, exporterUrl), true);
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('does not treat an imported module as the main module', () => {
|
||||
assert.equal(
|
||||
isMainModule(
|
||||
path.join(path.dirname(exporterPath), 'runner.py'),
|
||||
exporterUrl,
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(isMainModule(undefined, exporterUrl), false);
|
||||
});
|
||||
104
package-lock.json
generated
104
package-lock.json
generated
|
|
@ -70,7 +70,7 @@
|
|||
"msw": "^2.10.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^8.0.1",
|
||||
"playwright": "^1.57.0",
|
||||
"playwright": "1.61.1",
|
||||
"prettier": "^3.5.3",
|
||||
"react-devtools-core": "^6.1.5",
|
||||
"semver": "^7.7.2",
|
||||
|
|
@ -4217,53 +4217,6 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/playwright": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
|
||||
"integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.61.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/playwright-core": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
|
||||
"integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@polka/url": {
|
||||
"version": "1.0.0-next.29",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
|
||||
|
|
@ -21044,12 +20997,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
|
||||
"integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.58.2"
|
||||
"playwright-core": "1.61.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
|
|
@ -21077,6 +21031,7 @@
|
|||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
|
|
@ -21087,6 +21042,19 @@
|
|||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright/node_modules/playwright-core": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
|
||||
"integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/pluralize": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||
|
|
@ -28840,6 +28808,20 @@
|
|||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"packages/mobile-mcp/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"packages/mobile-mcp/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
|
|
@ -28888,6 +28870,24 @@
|
|||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"packages/mobile-mcp/node_modules/playwright": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.58.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"packages/mobile-mcp/node_modules/typescript": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@
|
|||
"msw": "^2.10.4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"patch-package": "^8.0.1",
|
||||
"playwright": "^1.57.0",
|
||||
"playwright": "1.61.1",
|
||||
"prettier": "^3.5.3",
|
||||
"react-devtools-core": "^6.1.5",
|
||||
"semver": "^7.7.2",
|
||||
|
|
|
|||
|
|
@ -69,6 +69,12 @@ const expectedManifest = {
|
|||
recommendedTests: ['integration-tests'],
|
||||
requiredConfigurations: ['node22'],
|
||||
},
|
||||
{
|
||||
paths: ['integration-tests/concurrent-runner/**'],
|
||||
domains: ['integration-tests'],
|
||||
recommendedTests: ['helper-tests'],
|
||||
requiredConfigurations: ['node22'],
|
||||
},
|
||||
{
|
||||
paths: ['.github/workflows/**'],
|
||||
domains: ['ci', 'workflows'],
|
||||
|
|
|
|||
|
|
@ -84,6 +84,11 @@ describe('isSafeHref', () => {
|
|||
expect(isSafeHref('javascript:alert(1)')).toBe(false);
|
||||
});
|
||||
|
||||
it('blocks file: scheme', () => {
|
||||
expect(isSafeHref('file://attacker.example/share/file.md')).toBe(false);
|
||||
expect(isSafeHref('file:///etc/passwd')).toBe(false);
|
||||
});
|
||||
|
||||
it('blocks data: URIs', () => {
|
||||
expect(isSafeHref('data:text/html,<script>alert(1)</script>')).toBe(false);
|
||||
});
|
||||
|
|
@ -145,6 +150,11 @@ describe('isSafeImageSrc', () => {
|
|||
expect(isSafeImageSrc('javascript:alert(1)')).toBe(false);
|
||||
});
|
||||
|
||||
it('blocks file: scheme', () => {
|
||||
expect(isSafeImageSrc('file:///tmp/private.png')).toBe(false);
|
||||
expect(isSafeImageSrc('file://attacker.example/share/img.png')).toBe(false);
|
||||
});
|
||||
|
||||
it('allows relative paths', () => {
|
||||
expect(isSafeImageSrc('/images/logo.png')).toBe(true);
|
||||
});
|
||||
|
|
|
|||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
|
|
@ -141,7 +141,7 @@ importers:
|
|||
specifier: ^8.0.1
|
||||
version: 8.0.1
|
||||
playwright:
|
||||
specifier: ^1.57.0
|
||||
specifier: 1.61.1
|
||||
version: 1.61.1
|
||||
prettier:
|
||||
specifier: ^3.5.3
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue