mirror of
https://github.com/cypress-io/cypress.git
synced 2026-04-26 17:08:10 -05:00
b86f1eac19
* 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 * wip: reproduce samesite conditions in e2e test * use tough-cookie@4.0.0 from git not on NPM yet, see issue: https://github.com/salesforce/tough-cookie/issues/182 * add more samesite proxy tests * update types + schema * run 2_cookies_spec also with forced samesite * use undefined not no_restriction * properly detect unspecified SameSite attribute * update snapshot * don't send sameSite on getAll calls * don't omit null/undefined props in automation * update comment * clean up SetCookieRequest logic * fix tests * fix unit test * update yarn.lock * rebalance e2e tests
231 lines
5.7 KiB
JavaScript
231 lines
5.7 KiB
JavaScript
const map = require('lodash/map')
|
|
const pick = require('lodash/pick')
|
|
const once = require('lodash/once')
|
|
const Promise = require('bluebird')
|
|
const browser = require('webextension-polyfill')
|
|
const client = require('./client')
|
|
const { getCookieUrl } = require('../lib/util')
|
|
|
|
const COOKIE_PROPS = ['url', 'name', 'path', 'secure', 'domain']
|
|
const GET_ALL_PROPS = COOKIE_PROPS.concat(['session', 'storeId'])
|
|
// https://developer.chrome.com/extensions/cookies#method-set
|
|
const SET_PROPS = COOKIE_PROPS.concat(['value', 'httpOnly', 'expirationDate', 'sameSite'])
|
|
|
|
const httpRe = /^http/
|
|
|
|
// normalize into null when empty array
|
|
const firstOrNull = (cookies) => {
|
|
return cookies[0] != null ? cookies[0] : null
|
|
}
|
|
|
|
const connect = function (host, path, extraOpts) {
|
|
const listenToCookieChanges = once(() => {
|
|
return browser.cookies.onChanged.addListener((info) => {
|
|
if (info.cause !== 'overwrite') {
|
|
return ws.emit('automation:push:request', 'change:cookie', info)
|
|
}
|
|
})
|
|
})
|
|
|
|
const fail = (id, err) => {
|
|
return ws.emit('automation:response', id, {
|
|
__error: err.message,
|
|
__stack: err.stack,
|
|
__name: err.name,
|
|
})
|
|
}
|
|
|
|
const invoke = function (method, id, ...args) {
|
|
const respond = (data) => {
|
|
return ws.emit('automation:response', id, { response: data })
|
|
}
|
|
|
|
return Promise.try(() => {
|
|
return automation[method].apply(automation, args.concat(respond))
|
|
}).catch((err) => {
|
|
return fail(id, err)
|
|
})
|
|
}
|
|
|
|
const ws = client.connect(host, path, extraOpts)
|
|
|
|
ws.on('automation:request', (id, msg, data) => {
|
|
switch (msg) {
|
|
case 'get:cookies':
|
|
return invoke('getCookies', id, data)
|
|
case 'get:cookie':
|
|
return invoke('getCookie', id, data)
|
|
case 'set:cookie':
|
|
return invoke('setCookie', id, data)
|
|
case 'clear:cookies':
|
|
return invoke('clearCookies', id, data)
|
|
case 'clear:cookie':
|
|
return invoke('clearCookie', id, data)
|
|
case 'is:automation:client:connected':
|
|
return invoke('verify', id, data)
|
|
case 'focus:browser:window':
|
|
return invoke('focus', id)
|
|
case 'take:screenshot':
|
|
return invoke('takeScreenshot', id)
|
|
default:
|
|
return fail(id, { message: `No handler registered for: '${msg}'` })
|
|
}
|
|
})
|
|
|
|
ws.on('connect', () => {
|
|
listenToCookieChanges()
|
|
|
|
return ws.emit('automation:client:connected')
|
|
})
|
|
|
|
return ws
|
|
}
|
|
|
|
const automation = {
|
|
connect,
|
|
|
|
getUrl: getCookieUrl,
|
|
|
|
clear (filter = {}) {
|
|
const clear = (cookie) => {
|
|
const url = this.getUrl(cookie)
|
|
const props = { url, name: cookie.name }
|
|
|
|
const throwError = function (err) {
|
|
throw (err != null ? err : new Error(`Removing cookie failed for: ${JSON.stringify(props)}`))
|
|
}
|
|
|
|
return Promise.try(() => {
|
|
return browser.cookies.remove(props)
|
|
}).then((details) => {
|
|
if (details) {
|
|
return cookie
|
|
}
|
|
|
|
return throwError()
|
|
}).catch(throwError)
|
|
}
|
|
|
|
return this.getAll(filter)
|
|
.map(clear)
|
|
},
|
|
|
|
getAll (filter = {}) {
|
|
filter = pick(filter, GET_ALL_PROPS)
|
|
|
|
return Promise.try(() => {
|
|
return browser.cookies.getAll(filter)
|
|
})
|
|
},
|
|
|
|
getCookies (filter, fn) {
|
|
return this.getAll(filter)
|
|
.then(fn)
|
|
},
|
|
|
|
getCookie (filter, fn) {
|
|
return this.getAll(filter)
|
|
.then(firstOrNull)
|
|
.then(fn)
|
|
},
|
|
|
|
setCookie (props = {}, fn) {
|
|
// only get the url if its not already set
|
|
if (props.url == null) {
|
|
props.url = this.getUrl(props)
|
|
}
|
|
|
|
if (props.hostOnly) {
|
|
delete props.domain
|
|
}
|
|
|
|
if (props.domain === 'localhost') {
|
|
delete props.domain
|
|
}
|
|
|
|
props = pick(props, SET_PROPS)
|
|
|
|
return Promise.try(() => {
|
|
return browser.cookies.set(props)
|
|
// the cookie callback could be null such as the
|
|
// case when expirationDate is before now
|
|
}).then((details) => {
|
|
return fn(details || null)
|
|
})
|
|
},
|
|
|
|
clearCookie (filter, fn) {
|
|
return this.clear(filter)
|
|
.then(firstOrNull)
|
|
.then(fn)
|
|
},
|
|
|
|
clearCookies (filter, fn) {
|
|
return this.clear(filter)
|
|
.then(fn)
|
|
},
|
|
|
|
focus (fn) {
|
|
// lets just make this simple and whatever is the current
|
|
// window bring that into focus
|
|
//
|
|
// TODO: if we REALLY want to be nice its possible we can
|
|
// figure out the exact window that's running Cypress but
|
|
// that's too much work with too little value at the moment
|
|
return Promise.try(() => {
|
|
return browser.windows.getCurrent()
|
|
}).then((window) => {
|
|
return browser.windows.update(window.id, { focused: true })
|
|
}).then(fn)
|
|
},
|
|
|
|
query (data) {
|
|
const code = `var s; (s = document.getElementById('${data.element}')) && s.textContent`
|
|
|
|
const queryTab = (tab) => {
|
|
return Promise.try(() => {
|
|
return browser.tabs.executeScript(tab.id, { code })
|
|
}).then((results) => {
|
|
if (!results || (results[0] !== data.string)) {
|
|
throw new Error('Executed script did not return result')
|
|
}
|
|
})
|
|
}
|
|
|
|
return Promise.try(() => {
|
|
return browser.tabs.query({ windowType: 'normal' })
|
|
}).filter((tab) => {
|
|
// the tab's url must begin with
|
|
// http or https so that we filter out
|
|
// about:blank and chrome:// urls
|
|
// which will throw errors!
|
|
return httpRe.test(tab.url)
|
|
}).then((tabs) => {
|
|
// generate array of promises
|
|
return map(tabs, queryTab)
|
|
}).any()
|
|
},
|
|
|
|
verify (data, fn) {
|
|
return this.query(data)
|
|
.then(fn)
|
|
},
|
|
|
|
lastFocusedWindow () {
|
|
return Promise.try(() => {
|
|
return browser.windows.getLastFocused()
|
|
})
|
|
},
|
|
|
|
takeScreenshot (fn) {
|
|
return this.lastFocusedWindow()
|
|
.then((win) => {
|
|
return browser.tabs.captureVisibleTab(win.id, { format: 'png' })
|
|
})
|
|
.then(fn)
|
|
},
|
|
|
|
}
|
|
|
|
module.exports = automation
|