Add support for sameSite in cookie-related commands (#6828)

* add experimental feature for sameSite

* allow experimental descriptions to render markdown

* sameSite support mostly working

* also strip sameSite from setCookie yielded value

* don't use `unspecified` - let browser set default

* add tests

* decaffeinate: Rename cdp_automation_spec.coffee from .coffee to .js

* decaffeinate: Convert cdp_automation_spec.coffee to JS

* decaffeinate: Run post-processing cleanups on cdp_automation_spec.coffee

* cleanup cdp_automation_spec.ts

* update unit tests

* update settings_spec to not render as markdown

* user-friendly error for insecure SameSite=None

* fix styling

* fix markdown renderer

* update types + schema

* use renderInline

* update experiment summary

* bind renderFn to md
This commit is contained in:
Zach Bloomquist
2020-03-27 10:04:50 -04:00
committed by GitHub
parent 54586f9e4a
commit b6703aaec7
19 changed files with 235 additions and 42 deletions
+5
View File
@@ -210,6 +210,11 @@
],
"default": "bundled",
"description": "If set to 'system', Cypress will try to find a Node.js executable on your path to use when executing your plugins. Otherwise, Cypress will use the Node version bundled with Cypress."
},
"experimentalGetCookiesSameSite": {
"type": "boolean",
"default": false,
"description": "If `true`, Cypress will add `sameSite` values to the objects yielded from `cy.setCookie()`, `cy.getCookie()`, and `cy.getCookies()`. This will become the default behavior in Cypress 5.0."
}
}
}
+10
View File
@@ -2450,6 +2450,12 @@ declare namespace Cypress {
* @default { runMode: 1, openMode: null }
*/
firefoxGcInterval: Nullable<number | { runMode: Nullable<number>, openMode: Nullable<number> }>
/**
* If `true`, Cypress will add `sameSite` values to the objects yielded from `cy.setCookie()`,
* `cy.getCookie()`, and `cy.getCookies()`. This will become the default behavior in Cypress 5.0.
* @default false
*/
experimentalGetCookiesSameSite: boolean
}
interface PluginConfigOptions extends ConfigOptions {
@@ -2594,12 +2600,15 @@ declare namespace Cypress {
onAnyAbort(route: RouteOptions, proxy: any): void
}
type SameSiteStatus = 'no_restriction' | 'strict' | 'lax'
interface SetCookieOptions extends Loggable, Timeoutable {
path: string
domain: string
secure: boolean
httpOnly: boolean
expiry: number
sameSite: SameSiteStatus
}
/**
@@ -4701,6 +4710,7 @@ declare namespace Cypress {
httpOnly: boolean
secure: boolean
expiry?: string
sameSite?: SameSiteStatus
}
interface EnqueuedCommand {
@@ -687,9 +687,7 @@ describe('Settings', () => {
// do not overwrite the shared object reference -
// because it is used by the app's code.
this.win.experimental.names.experimentalCoolFeature = 'Cool Feature'
this.win.experimental.summaries.experimentalCoolFeature = `
Enables super cool feature from Cypress where you can see the cool feature
`
this.win.experimental.summaries.experimentalCoolFeature = 'Enables super cool feature from Cypress where you can see the cool feature'
})
const hasLearnMoreLink = () => {
@@ -26,10 +26,17 @@ export default class MarkdownRenderer extends React.PureComponent {
}
render () {
let renderFn = md.render
if (this.props.noParagraphWrapper) {
// prevent markdown-it from wrapping the output in a <p> tag
renderFn = md.renderInline
}
return (
<span ref={(node) => this.node = node}
dangerouslySetInnerHTML={{
__html: md.render(this.props.markdown),
__html: renderFn.call(md, this.props.markdown),
}}>
</span>
)
@@ -3,6 +3,7 @@ import React from 'react'
import { observer } from 'mobx-react'
import ipc from '../lib/ipc'
import { getExperiments } from '@packages/server/lib/experiments'
import MarkdownRenderer from '../lib/markdown-renderer'
const openHelp = (e) => {
e.preventDefault()
@@ -27,14 +28,14 @@ const Experiments = observer(({ project }) => {
_.map(experiments, (experiment, i) => (
<li className='experiment' key={i}>
<h5>
{experiment.name}
<MarkdownRenderer markdown={experiment.name} noParagraphWrapper/>
<span className={`experiment-status-sign ${experiment.enabled ? 'enabled' : ''}`}>
{experiment.enabled ? 'ON' : 'OFF'}
</span>
</h5>
<div className='experiment-desc'>
<p className="text-muted">
{experiment.summary}
<MarkdownRenderer markdown={experiment.summary} noParagraphWrapper/>
</p>
<div className='experiment-status'>
<code>{experiment.key}</code>
@@ -247,7 +247,7 @@
.experiment-intro {
padding-bottom: 15px;
margin-bottom: 0px;
margin-bottom: 0px;
border-bottom: 1px solid #ddd;
}
@@ -265,7 +265,7 @@
p {
width: 80%;
margin-bottom: 0;
margin-bottom: 0;
}
.experiment-status {
@@ -273,7 +273,7 @@
}
}
.experiment-status-sign {
margin-left: 10px;
color: #9d9ea9;
+62 -1
View File
@@ -5,7 +5,7 @@ const $utils = require('../../cypress/utils')
const $errUtils = require('../../cypress/error_utils')
const $Location = require('../../cypress/location')
const COOKIE_PROPS = 'name value path secure httpOnly expiry domain'.split(' ')
const COOKIE_PROPS = 'name value path secure httpOnly expiry domain sameSite'.split(' ')
const commandNameRe = /(:)(\w)/
@@ -33,7 +33,37 @@ const mergeDefaults = function (obj) {
return merge(obj)
}
// from https://developer.chrome.com/extensions/cookies#type-SameSiteStatus
// note that `unspecified` is purposely omitted - Firefox and Chrome set
// different defaults, and Firefox lacks support for `unspecified`, so
// `undefined` is used in lieu of `unspecified`
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1624668
const VALID_SAMESITE_VALUES = ['no_restriction', 'lax', 'strict']
const normalizeSameSite = (sameSite) => {
if (_.isUndefined(sameSite)) {
return sameSite
}
if (_.isString(sameSite)) {
sameSite = sameSite.toLowerCase()
}
if (sameSite === 'none') {
// "None" is the value sent in the header for `no_restriction`, so allow it here for convenience
sameSite = 'no_restriction'
}
return sameSite
}
module.exports = function (Commands, Cypress, cy, state, config) {
const maybeStripSameSiteProp = (cookie) => {
if (cookie && !Cypress.config('experimentalGetCookiesSameSite')) {
delete cookie.sameSite
}
}
const automateCookies = function (event, obj = {}, log, timeout) {
const automate = () => {
return Cypress.automation(event, mergeDefaults(obj))
@@ -146,6 +176,8 @@ module.exports = function (Commands, Cypress, cy, state, config) {
return automateCookies('get:cookie', { name }, options._log, options.timeout)
.then((resp) => {
maybeStripSameSiteProp(resp)
options.cookie = resp
return resp
@@ -181,6 +213,10 @@ module.exports = function (Commands, Cypress, cy, state, config) {
return automateCookies('get:cookies', _.pick(options, 'domain'), options._log, options.timeout)
.then((resp) => {
if (Array.isArray(resp)) {
resp.forEach(maybeStripSameSiteProp)
}
options.cookies = resp
return resp
@@ -225,12 +261,37 @@ module.exports = function (Commands, Cypress, cy, state, config) {
const onFail = options._log
cookie.sameSite = normalizeSameSite(cookie.sameSite)
if (!_.isUndefined(cookie.sameSite) && !VALID_SAMESITE_VALUES.includes(cookie.sameSite)) {
$errUtils.throwErrByPath('setCookie.invalid_samesite', {
onFail,
args: {
value: options.sameSite, // for clarity, throw the error with the user's unnormalized option
validValues: VALID_SAMESITE_VALUES,
},
})
}
// cookies with SameSite=None must also set Secure
// @see https://web.dev/samesite-cookies-explained/#changes-to-the-default-behavior-without-samesite
if (cookie.sameSite === 'no_restriction' && cookie.secure !== true) {
$errUtils.throwErrByPath('setCookie.secure_not_set_with_samesite_none', {
onFail,
args: {
value: options.sameSite, // for clarity, throw the error with the user's unnormalized option
},
})
}
if (!_.isString(name) || !_.isString(value)) {
$errUtils.throwErrByPath('setCookie.invalid_arguments', { onFail })
}
return automateCookies('set:cookie', cookie, options._log, options.timeout)
.then((resp) => {
maybeStripSameSiteProp(resp)
options.cookie = resp
return resp
@@ -1186,6 +1186,23 @@ module.exports = {
message: "#{cmd('setCookie')} must be passed an RFC-6265-compliant cookie value. You passed:\n\n`{{value}}`"
docsUrl: "https://on.cypress.io/setcookie"
}
invalid_samesite: ({ validValues, value }) => {
message: """
If a `sameSite` value is supplied to #{cmd('setCookie')}, it must be a string from the following list:
> #{validValues.join(', ')}
You passed:
> #{format(value)}
"""
docsUrl: "https://on.cypress.io/setcookie"
}
secure_not_set_with_samesite_none: ({ validValues, value }) => {
message: """
Only cookies with the `secure` flag set to `true` can use `sameSite: '{{value}}'`.
Pass `secure: true` to #{cmd('setCookie')} to set a cookie with `sameSite: '{{value}}'`.
"""
docsUrl: "https://on.cypress.io/setcookie"
}
should:
chainer_not_found: "The chainer `{{chainer}}` was not found. Could not build assertion."
@@ -371,7 +371,7 @@ describe "src/cy/commands/cookies", ->
}).then ->
expect(Cypress.automation).to.be.calledWith(
"set:cookie",
{ domain: "localhost", name: "foo", value: "bar", path: "/", secure: false, httpOnly: false, expiry: 12345 }
{ domain: "localhost", name: "foo", value: "bar", path: "/", secure: false, httpOnly: false, expiry: 12345, sameSite: undefined }
)
it "can change options", ->
@@ -384,7 +384,7 @@ describe "src/cy/commands/cookies", ->
}).then ->
expect(Cypress.automation).to.be.calledWith(
"set:cookie",
{ domain: "brian.dev.local", name: "foo", value: "bar", path: "/foo", secure: true, httpOnly: true, expiry: 987 }
{ domain: "brian.dev.local", name: "foo", value: "bar", path: "/foo", secure: true, httpOnly: true, expiry: 987, sameSite: undefined }
)
it "does not mutate options", ->
@@ -394,6 +394,33 @@ describe "src/cy/commands/cookies", ->
cy.setCookie("foo", "bar", {}).then ->
expect(options).deep.eq({})
it "can set cookies with sameSite", ->
Cypress.automation.restore()
Cypress.utils.addTwentyYears.restore()
Cypress.sinon.stub(Cypress, 'config').callThrough()
.withArgs('experimentalGetCookiesSameSite').returns(true)
cy.setCookie('one', 'bar', { sameSite: 'none', secure: true })
cy.getCookie('one').should('include', { sameSite: 'no_restriction' })
cy.setCookie('two', 'bar', { sameSite: 'no_restriction', secure: true })
cy.getCookie('two').should('include', { sameSite: 'no_restriction' })
cy.setCookie('three', 'bar', { sameSite: 'Lax' })
cy.getCookie('three').should('include', { sameSite: 'lax' })
cy.setCookie('four', 'bar', { sameSite: 'Strict' })
cy.getCookie('four').should('include', { sameSite: 'strict' })
cy.setCookie('five', 'bar')
## @see https://bugzilla.mozilla.org/show_bug.cgi?id=1624668
if Cypress.isBrowser('firefox')
cy.getCookie('five').should('include', { sameSite: 'no_restriction' })
else
cy.getCookie('five').should('not.have.property', 'sameSite')
describe "timeout", ->
it "sets timeout to Cypress.config(responseTimeout)", ->
Cypress.config("responseTimeout", 2500)
@@ -497,6 +524,39 @@ describe "src/cy/commands/cookies", ->
cy.setCookie("foo", 123)
it "when an invalid samesite prop is supplied", (done) ->
cy.on "fail", (err) =>
lastLog = @lastLog
expect(@logs.length).to.eq(1)
expect(lastLog.get("error").message).to.eq """
If a `sameSite` value is supplied to `cy.setCookie()`, it must be a string from the following list:
> no_restriction, lax, strict
You passed:
> bad
"""
expect(lastLog.get("error").docsUrl).to.eq "https://on.cypress.io/setcookie"
expect(lastLog.get("error")).to.eq(err)
done()
cy.setCookie('foo', 'bar', { sameSite: 'bad' })
it "when samesite=none is supplied and secure is not set", (done) ->
cy.on "fail", (err) =>
lastLog = @lastLog
expect(@logs.length).to.eq(1)
expect(lastLog.get("error").message).to.eq """
Only cookies with the `secure` flag set to `true` can use `sameSite: 'None'`.
Pass `secure: true` to `cy.setCookie()` to set a cookie with `sameSite: 'None'`.
"""
expect(lastLog.get("error").docsUrl).to.eq "https://on.cypress.io/setcookie"
expect(lastLog.get("error")).to.eq(err)
done()
cy.setCookie('foo', 'bar', { sameSite: 'None' })
context "when setting an invalid cookie", ->
it "throws an error if the backend responds with an error", (done) ->
err = new Error("backend could not set cookie")
@@ -520,7 +580,7 @@ describe "src/cy/commands/cookies", ->
Cypress.automation
.withArgs("set:cookie", {
domain: "localhost", name: "foo", value: "bar", path: "/", secure: false, httpOnly: false, expiry: 12345
domain: "localhost", name: "foo", value: "bar", path: "/", secure: false, httpOnly: false, expiry: 12345, sameSite: undefined
})
.resolves({
name: "foo", value: "bar", domain: "localhost", path: "/", secure: true, httpOnly: false
+1 -1
View File
@@ -6,7 +6,7 @@ const browser = require('webextension-polyfill')
const client = require('./client')
const { getCookieUrl } = require('../lib/util')
const COOKIE_PROPS = ['url', 'name', 'path', 'secure', 'domain']
const COOKIE_PROPS = ['url', 'name', 'path', 'secure', 'domain', 'sameSite']
const GET_ALL_PROPS = COOKIE_PROPS.concat(['session', 'storeId'])
const SET_PROPS = COOKIE_PROPS.concat(['value', 'httpOnly', 'expirationDate'])
+1 -1
View File
@@ -5,7 +5,7 @@ const debug = require('debug')('cypress:server:cookies')
// match the w3c webdriver spec on return cookies
// https://w3c.github.io/webdriver/webdriver-spec.html#cookies
const COOKIE_PROPERTIES = 'name value path domain secure httpOnly expiry hostOnly'.split(' ')
const COOKIE_PROPERTIES = 'name value path domain secure httpOnly expiry hostOnly sameSite'.split(' ')
const normalizeCookies = (cookies) => {
return _.map(cookies, normalizeCookieProps)
+26 -10
View File
@@ -6,15 +6,26 @@ import debugModule from 'debug'
const debugVerbose = debugModule('cypress-verbose:server:browsers:cdp_automation')
export interface CyCookie {
name: string
value: string
expirationDate: number
hostOnly: boolean
domain: string
path: string
secure: boolean
httpOnly: boolean
export type CyCookie = Pick<chrome.cookies.Cookie, 'name' | 'value' | 'expirationDate' | 'hostOnly' | 'domain' | 'path' | 'secure' | 'httpOnly' | 'sameSite'>
function convertSameSiteExtensionToCdp (str: chrome.cookies.SameSiteStatus): Optional<cdp.Network.CookieSameSite> {
return ({
'no_restriction': 'None',
'lax': 'Lax',
'strict': 'Strict',
})[str]
}
function convertSameSiteCdpToExtension (str: cdp.Network.CookieSameSite): chrome.cookies.SameSiteStatus {
if (_.isUndefined(str)) {
return str
}
if (str === 'None') {
return 'no_restriction'
}
return str.toLowerCase() as chrome.cookies.SameSiteStatus
}
// Cypress uses the webextension-style filtering
@@ -52,6 +63,9 @@ export const CdpAutomation = (sendDebuggerCommandFn: SendDebuggerCommand) => {
delete cookie.expires
}
// @ts-ignore
cookie.sameSite = convertSameSiteCdpToExtension(cookie.sameSite)
// @ts-ignore
cookie.expirationDate = cookie.expires
delete cookie.expires
@@ -76,6 +90,8 @@ export const CdpAutomation = (sendDebuggerCommandFn: SendDebuggerCommand) => {
// @ts-ignore
cookie.expires = cookie.expirationDate
// @ts-ignore
cookie.sameSite = convertSameSiteExtensionToCdp(cookie.sameSite)
// without this logic, a cookie being set on 'foo.com' will only be set for 'foo.com', not other subdomains
if (!cookie.hostOnly && cookie.domain[0] !== '.') {
@@ -93,7 +109,7 @@ export const CdpAutomation = (sendDebuggerCommandFn: SendDebuggerCommand) => {
delete cookie.hostOnly
delete cookie.expirationDate
return cookie
return cookie as any
}
const getAllCookies = (filter: CyCookieFilter) => {
+3 -1
View File
@@ -94,7 +94,7 @@ systemConfigKeys = toWords """
# Know experimental flags / values
# each should start with "experimental" and be camel cased
# example: experimentalComponentTesting
experimentalConfigKeys = []
experimentalConfigKeys = ['experimentalGetCookiesSameSite']
CONFIG_DEFAULTS = {
port: null
@@ -154,6 +154,7 @@ CONFIG_DEFAULTS = {
## experimental keys (should all start with "experimental" prefix)
# example for component testing with subkeys
# experimentalComponentTesting: { componentFolder: 'cypress/component' }
experimentalGetCookiesSameSite: false
}
validationRules = {
@@ -194,6 +195,7 @@ validationRules = {
watchForFileChanges: v.isBoolean
firefoxGcInterval: v.isValidFirefoxGcInterval
# experimental flag validation below
experimentalGetCookiesSameSite: v.isBoolean
}
convertRelativeToAbsolutePaths = (projectRoot, obj, defaults = {}) ->
+6 -2
View File
@@ -50,7 +50,9 @@ interface StringValues {
}
```
*/
const _summaries: StringValues = {}
const _summaries: StringValues = {
experimentalGetCookiesSameSite: 'Adds `sameSite` values to the objects yielded from `cy.setCookie()`, `cy.getCookie()`, and `cy.getCookies()`. This will become the default behavior in Cypress 5.0.',
}
/**
* Keeps short names for experiments. When adding new experiments, add a short name.
@@ -62,7 +64,9 @@ const _summaries: StringValues = {}
}
```
*/
const _names: StringValues = {}
const _names: StringValues = {
experimentalGetCookiesSameSite: 'Set `sameSite` property when retrieving cookies',
}
/**
* Export this object for easy stubbing from end-to-end tests.
+10
View File
@@ -19,6 +19,13 @@ TLS_VERSION_ERROR_RE = /TLSV1_ALERT_PROTOCOL_VERSION|UNSUPPORTED_PROTOCOL/
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
## sameSite from tough-cookie is slightly different from webextension API
convertSameSiteToughToExtension = (str) =>
if str is "none"
return "no_restriction"
return str
getOriginalHeaders = (req = {}) ->
## the request instance holds an instance
## of the original ClientRequest
@@ -492,6 +499,9 @@ module.exports = (options = {}) ->
if expiry <= 0
return automationFn('clear:cookie', cookie)
cookie.sameSite = convertSameSiteToughToExtension(cookie.sameSite)
automationFn('set:cookie', cookie)
sendStream: (headers, automationFn, options = {}) ->
+1 -1
View File
@@ -140,7 +140,7 @@
"@packages/static": "*",
"@packages/ts": "*",
"@types/chai-as-promised": "7.1.2",
"@types/chrome": "0.0.91",
"@types/chrome": "0.0.101",
"@types/node": "8.10.55",
"babel-plugin-add-module-exports": "1.0.2",
"babelify": "10.0.0",
@@ -95,8 +95,8 @@ context('lib/browsers/cdp_automation', () => {
return this.onRequest('get:cookies', { domain: 'localhost' })
.then((resp) => {
expect(resp).to.deep.eq([
{ name: 'foo', value: 'f', path: '/', domain: 'localhost', secure: true, httpOnly: true, expirationDate: 123 },
{ name: 'bar', value: 'b', path: '/', domain: 'localhost', secure: false, httpOnly: false, expirationDate: 456 },
{ name: 'foo', value: 'f', path: '/', domain: 'localhost', secure: true, httpOnly: true, expirationDate: 123, sameSite: undefined },
{ name: 'bar', value: 'b', path: '/', domain: 'localhost', secure: false, httpOnly: false, expirationDate: 456, sameSite: undefined },
])
})
})
@@ -115,7 +115,7 @@ context('lib/browsers/cdp_automation', () => {
it('returns a specific cookie by name', function () {
return this.onRequest('get:cookie', { domain: 'google.com', name: 'session' })
.then((resp) => {
expect(resp).to.deep.eq({ name: 'session', value: 'key', path: '/login', domain: 'google.com', secure: true, httpOnly: true, expirationDate: 123 })
expect(resp).to.deep.eq({ name: 'session', value: 'key', path: '/login', domain: 'google.com', secure: true, httpOnly: true, expirationDate: 123, sameSite: undefined })
})
})
@@ -129,9 +129,9 @@ context('lib/browsers/cdp_automation', () => {
describe('set:cookie', () => {
beforeEach(function () {
this.sendDebuggerCommand.withArgs('Network.setCookie', { domain: '.google.com', name: 'session', value: 'key', path: '/', expires: undefined })
this.sendDebuggerCommand.withArgs('Network.setCookie', { domain: '.google.com', name: 'session', value: 'key', path: '/', expires: undefined, sameSite: undefined })
.resolves({ success: true })
.withArgs('Network.setCookie', { domain: 'foo', path: '/bar', name: '', value: '', expires: undefined })
.withArgs('Network.setCookie', { domain: 'foo', path: '/bar', name: '', value: '', expires: undefined, sameSite: undefined })
.rejects(new Error('some error'))
.withArgs('Network.getAllCookies')
.resolves({
@@ -144,7 +144,7 @@ context('lib/browsers/cdp_automation', () => {
it('resolves with the cookie props', function () {
return this.onRequest('set:cookie', { domain: 'google.com', name: 'session', value: 'key', path: '/' })
.then((resp) => {
expect(resp).to.deep.eq({ domain: '.google.com', expirationDate: undefined, httpOnly: false, name: 'session', value: 'key', path: '/', secure: false })
expect(resp).to.deep.eq({ domain: '.google.com', expirationDate: undefined, httpOnly: false, name: 'session', value: 'key', path: '/', secure: false, sameSite: undefined })
})
})
@@ -178,7 +178,7 @@ context('lib/browsers/cdp_automation', () => {
return this.onRequest('clear:cookie', { domain: 'google.com', name: 'session' })
.then((resp) => {
expect(resp).to.deep.eq(
{ name: 'session', value: 'key', path: '/', domain: 'google.com', secure: true, httpOnly: true, expirationDate: 123 },
{ name: 'session', value: 'key', path: '/', domain: 'google.com', secure: true, httpOnly: true, expirationDate: 123, sameSite: undefined },
)
})
})
@@ -205,7 +205,7 @@ context('lib/browsers/cdp_automation', () => {
this.sendDebuggerCommand.withArgs('Browser.getVersion').resolves({ protocolVersion: '1.3' })
this.sendDebuggerCommand.withArgs('Page.captureScreenshot').resolves({ data: 'foo' })
expect(this.onRequest('take:screenshot'))
return expect(this.onRequest('take:screenshot'))
.to.eventually.equal('data:image/png;base64,foo')
})
@@ -213,7 +213,7 @@ context('lib/browsers/cdp_automation', () => {
this.sendDebuggerCommand.withArgs('Browser.getVersion').resolves({ protocolVersion: '1.3' })
this.sendDebuggerCommand.withArgs('Page.captureScreenshot').rejects()
expect(this.onRequest('take:screenshot'))
return expect(this.onRequest('take:screenshot'))
.to.be.rejectedWith('The browser responded with an error when Cypress attempted to take a screenshot.')
})
})
@@ -797,6 +797,7 @@ describe "lib/config", ->
requestTimeout: { value: 5000, from: "default" },
responseTimeout: { value: 30000, from: "default" },
execTimeout: { value: 60000, from: "default" },
experimentalGetCookiesSameSite: { value: false, from: "default" },
taskTimeout: { value: 60000, from: "default" },
numTestsKeptInMemory: { value: 50, from: "default" },
waitForAnimations: { value: true, from: "default" },
@@ -866,6 +867,7 @@ describe "lib/config", ->
requestTimeout: { value: 5000, from: "default" },
responseTimeout: { value: 30000, from: "default" },
execTimeout: { value: 60000, from: "default" },
experimentalGetCookiesSameSite: { value: false, from: "default" },
taskTimeout: { value: 60000, from: "default" },
numTestsKeptInMemory: { value: 50, from: "default" },
waitForAnimations: { value: true, from: "default" },
+4 -4
View File
@@ -3496,10 +3496,10 @@
dependencies:
"@types/node" "*"
"@types/chrome@0.0.91":
version "0.0.91"
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.91.tgz#4b3996f55f344057e6a677c8366aa98080c6e380"
integrity sha512-vNvo9lJkp1AvViWrUwe1bxhoMwr5dRZWlgr1DTuaNkz97LsG56lDX1sceWeZir2gRACJ5vdHtoRdVAvm8C75Ug==
"@types/chrome@0.0.101":
version "0.0.101"
resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.101.tgz#8b6f7d4f1d4890ba7d950f8492725fa7ba9ab910"
integrity sha512-9GpAt3fBVPdzEIwdgDrxqCaURyJqOVz+oIWB+iDE2FD0ZeGQhkMwTqJIW+Ok/lnie6tt6DwVMZkOS8x6QkmWsQ==
dependencies:
"@types/filesystem" "*"