@@ -150,6 +150,17 @@ export function escapeRegExp(str: string): string {
150150 return str . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) ;
151151}
152152
153+ // 转义字符串,使其能安全嵌入到 shell 双引号 "..." 语境中而不被提前截断/破坏结构。
154+ // 命令最终是通过 child_process.exec(内部走系统 shell,如 sh -c "...")执行的,
155+ // 若 --grep="${pattern}" 中的 pattern 本身包含双引号 "、反斜杠 \、美元符 $ 或反引号 `,
156+ // 这些字符在 POSIX shell 双引号语境下有特殊含义(提前结束引用、变量替换、命令替换等),
157+ // 会导致 shell 把命令切分成错误的参数(例如用例标题中常见的 点击"了解详情" 这种带英文引号的步骤描述),
158+ // 使 Playwright 收到被截断/错乱的 --grep 值,从而匹配不到任何用例,抛出 "No tests found." 错误。
159+ // 因此在拼接进双引号字符串前,需要对这四个字符做反斜杠转义。
160+ export function escapeShellDoubleQuoted ( str : string ) : string {
161+ return str . replace ( / [ \\ $ ` " ] / g, "\\$&" ) ;
162+ }
163+
153164export function encodeQueryParams ( url : string ) : string {
154165 // 查找问号位置
155166 const questionMarkIndex = url . indexOf ( '?' ) ;
@@ -411,15 +422,19 @@ export function generateCommands(
411422 const fileMode = process . env . TESTSOLAR_TTP_FILEMODE == "1" ;
412423
413424 // 获取 grep 模式(在 fileMode 下不使用 grep)
414- // 注意:Playwright 会把 --grep 的值当作正则表达式(forceRegExp 内部执行 new RegExp(pattern, "gi"))。
415- // 用例标题中常见的 "(" ")" "." "*" "?" "+" "[" "]" "{" "}" "^" "$" "|" "\" 在正则中都有特殊含义,
416- // 若不转义会导致用例标题(比如带 "(60087660)" 这种 ID 后缀)无法与真实标题正确匹配,
417- // Playwright 过滤后测试数为 0,进而抛出 "No tests found." 错误。
418- // 因此这里对每个用例名单独转义正则特殊字符,再用未转义的 "|" 拼接多个用例做 OR 匹配。
425+ // 注意有两层转义需要处理:
426+ // 1) 正则层面:Playwright 会把 --grep 的值当作正则表达式(forceRegExp 内部执行 new RegExp(pattern, "gi"))。
427+ // 用例标题中常见的 "(" ")" "." "*" "?" "+" "[" "]" "{" "}" "^" "$" "|" "\" 在正则中都有特殊含义,
428+ // 若不转义会导致用例标题(比如带 "(60087660)" 这种 ID 后缀)无法与真实标题正确匹配。
429+ // 2) shell 层面:command 最终通过 child_process.exec(内部走系统 shell)执行,
430+ // --grep="${pattern}" 整体处于 shell 双引号语境中,若 pattern 含有英文双引号 "
431+ // (比如用例标题里常见的 点击"了解详情" 这种步骤描述),会被 shell 当作引号边界提前截断/错乱整条命令。
432+ // 因此正则转义之后,还需要再做一次 shell 双引号语境转义,才能安全拼进 --grep="..."。
433+ // 两者叠加缺一,都会导致 Playwright 过滤后测试数为 0,进而抛出 "No tests found." 错误。
419434 let grepPattern = "" ;
420435 if ( testCases . length > 0 && ! fileMode && ! runAllCases ) {
421436 const escapedPattern = testCases
422- . map ( ( testCase ) => escapeRegExp ( decodeURIComponent ( testCase ) ) )
437+ . map ( ( testCase ) => escapeShellDoubleQuoted ( escapeRegExp ( decodeURIComponent ( testCase ) ) ) )
423438 . join ( "|" ) ;
424439 grepPattern = `--grep="${ escapedPattern } "` ;
425440 }
0 commit comments