From 4c0371f4b54f540393819b0d5db884163a16d0ad Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Wed, 12 Jul 2023 13:09:49 -0400 Subject: [PATCH] fix: Handle evaled privileged commands (#27267) --- cli/CHANGELOG.md | 10 +++++- .../cypress/e2e/e2e/privileged_commands.cy.ts | 17 ++++++++++ .../privileged-commands/privileged-channel.js | 32 ++++++++++++++++--- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 56291616d993..ac2728f2a6a9 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,7 +1,15 @@ +## 12.17.2 + +_Released 07/18/2023 (PENDING)_ + +**Bugfixes:** + +- Fixed an issue where `cy.writeFile()` would erroneously fail with the error `cy.writeFile() must only be invoked from the spec file or support file`. Fixes [#27097](https://github.com/cypress-io/cypress/issues/27097). + ## 12.17.1 -_Released 07/18/2023_ +_Released 07/10/2023_ **Bugfixes:** diff --git a/packages/driver/cypress/e2e/e2e/privileged_commands.cy.ts b/packages/driver/cypress/e2e/e2e/privileged_commands.cy.ts index de1db4a9a930..d797e4724978 100644 --- a/packages/driver/cypress/e2e/e2e/privileged_commands.cy.ts +++ b/packages/driver/cypress/e2e/e2e/privileged_commands.cy.ts @@ -115,6 +115,23 @@ describe('privileged commands', () => { cy.get('#basic').selectFile(Uint8Array.from([98, 97, 122])) }) + it('handles evaled code', () => { + window.eval(` + cy.task('return:arg', 'eval arg') + .then(() => { + cy.task('return:arg', 'then eval arg') + }) + + cy.get('body') + .each(() => { + cy.task('return:arg', 'each eval arg') + }) + .within(() => { + cy.task('return:arg', 'within eval arg') + }) + `) + }) + it('passes in test body .then() callback', () => { cy.then(() => { cy.exec('echo "hello"') diff --git a/packages/server/lib/privileged-commands/privileged-channel.js b/packages/server/lib/privileged-commands/privileged-channel.js index b9bb7baabe75..5f908d0d28ad 100644 --- a/packages/server/lib/privileged-commands/privileged-channel.js +++ b/packages/server/lib/privileged-commands/privileged-channel.js @@ -30,6 +30,8 @@ const queryStringRegex = /\?.*$/ + let hasValidCallbackContext = false + // since this function is eval'd, the scripts are included as stringified JSON if (scripts) { scripts = parse(scripts) @@ -69,6 +71,18 @@ return filteredLines.length > 0 } + const isInCallback = (err) => { + return stringIncludes.call(err.stack, 'thenFn@') || stringIncludes.call(err.stack, 'withinFn@') + } + + const hasCallbackInsideEval = (err) => { + if (browserFamily === 'webkit') { + return isInCallback(err) && hasValidCallbackContext + } + + return isInCallback(err) && stringIncludes.call(err.stack, '> eval line') + } + // in non-chromium browsers, the stack will include either the spec file url // or the support file const hasStackLinesFromSpecOrSupportFile = (err) => { @@ -96,16 +110,22 @@ 'task', ] + const callbackCommands = [ + 'each', + 'then', + 'within', + ] + function stackIsFromSpecFrame (err) { if (isSpecBridge) { return hasSpecBridgeInvocation(err) } if (browserFamily === 'chromium') { - return hasSpecFrameStackLines(err) + return hasStackLinesFromSpecOrSupportFile(err) || hasSpecFrameStackLines(err) } - return hasStackLinesFromSpecOrSupportFile(err) + return hasCallbackInsideEval(err) || hasStackLinesFromSpecOrSupportFile(err) } // source: https://github.com/bryc/code/blob/d0dac1c607a005679799024ff66166e13601d397/jshash/experimental/cyrb53.js @@ -141,8 +161,6 @@ } async function onCommandInvocation (command) { - if (!arrayIncludes.call(privilegedCommands, command.name)) return - // message doesn't really matter since we're only interested in the stack const err = new Err('command stack error') @@ -152,6 +170,12 @@ captureStackTrace.call(Err, err, onCommandInvocation) } + if (arrayIncludes.call(callbackCommands, command.name)) { + hasValidCallbackContext = stackIsFromSpecFrame(err) + } + + if (!arrayIncludes.call(privilegedCommands, command.name)) return + // if stack is not validated as being from the spec frame, don't add // it as a verified command if (!stackIsFromSpecFrame(err)) return