mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-05 22:19:46 -06:00
misc: (studio) add support for url routing (#31205)
* update url with studio params * updates * spec updates * adding tests * updating changelog * skip adding visit log during start * update url support * cy origin tests * fix tests * updates * update origin test * add a wait * update * pr updates --------- Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
This commit is contained in:
@@ -10,6 +10,7 @@ _Released 3/11/2025 (PENDING)_
|
||||
**Misc:**
|
||||
|
||||
- Additional CLI options will be displayed in the terminal for some Cloud error messages. Addressed in [#31211](https://github.com/cypress-io/cypress/pull/31211).
|
||||
- Updated Cypress Studio with url routing to support maintaining state when reloading. Addresses [#31000](https://github.com/cypress-io/cypress/issues/31000) and [#30996](https://github.com/cypress-io/cypress/issues/30996).
|
||||
|
||||
**Dependency Updates:**
|
||||
|
||||
|
||||
@@ -26,9 +26,13 @@ export default defineConfig({
|
||||
framework: 'vue',
|
||||
},
|
||||
},
|
||||
hosts: {
|
||||
'foobar.com': '127.0.0.1',
|
||||
},
|
||||
'e2e': {
|
||||
experimentalRunAllSpecs: true,
|
||||
experimentalStudio: true,
|
||||
experimentalOriginDependencies: true,
|
||||
baseUrl: 'http://localhost:5555',
|
||||
supportFile: 'cypress/e2e/support/e2eSupport.ts',
|
||||
async setupNodeEvents (on, config) {
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
export function launchStudio () {
|
||||
export function launchStudio ({ specName = 'spec.cy.js', createNewTest = false, cliArgs = [''] } = {}) {
|
||||
cy.scaffoldProject('experimental-studio')
|
||||
cy.openProject('experimental-studio')
|
||||
cy.openProject('experimental-studio', cliArgs)
|
||||
cy.startAppServer('e2e')
|
||||
cy.visitApp()
|
||||
cy.specsPageIsVisible()
|
||||
cy.get(`[data-cy-row="spec.cy.js"]`).click()
|
||||
cy.get(`[data-cy-row="${specName}"]`).click()
|
||||
|
||||
cy.waitForSpecToFinish()
|
||||
|
||||
// Should not show "Studio Commands" until we've started a new Studio session.
|
||||
cy.get('[data-cy="hook-name-studio commands"]').should('not.exist')
|
||||
|
||||
cy
|
||||
.contains('visits a basic html page')
|
||||
.closest('.runnable-wrapper')
|
||||
if (createNewTest) {
|
||||
cy.contains('studio functionality').as('item')
|
||||
} else {
|
||||
cy.contains('visits a basic html page').as('item')
|
||||
}
|
||||
|
||||
cy.get('@item')
|
||||
.closest('.runnable-wrapper').as('runnable-wrapper')
|
||||
.realHover()
|
||||
|
||||
cy.get('@runnable-wrapper')
|
||||
.findByTestId('launch-studio')
|
||||
.click()
|
||||
|
||||
// Studio re-executes spec before waiting for commands - wait for the spec to finish executing.
|
||||
cy.waitForSpecToFinish()
|
||||
|
||||
cy.get('[data-cy="hook-name-studio commands"]').should('exist')
|
||||
if (createNewTest) {
|
||||
cy.get('span.runnable-title').contains('New Test').should('exist')
|
||||
} else {
|
||||
cy.get('[data-cy="hook-name-studio commands"]').should('exist')
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -315,6 +315,8 @@ export class EventManager {
|
||||
this.ws.emit('studio:save', saveInfo, (err) => {
|
||||
if (err) {
|
||||
this.reporterBus.emit('test:set:state', this.studioStore.saveError(err), noop)
|
||||
} else {
|
||||
this.studioStore.saveSuccess()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -419,7 +421,7 @@ export class EventManager {
|
||||
|
||||
const hideCommandLog = Cypress.config('hideCommandLog')
|
||||
|
||||
this.studioStore.initialize(config, runState)
|
||||
this.studioStore.initialize(config)
|
||||
|
||||
const runnables = Cypress.runner.normalizeAll(runState.tests, hideCommandLog, testFilter)
|
||||
|
||||
@@ -485,14 +487,7 @@ export class EventManager {
|
||||
|
||||
return new Bluebird((resolve) => {
|
||||
this.reporterBus.emit('reporter:collect:run:state', (reporterState: ReporterRunState) => {
|
||||
resolve({
|
||||
...reporterState,
|
||||
studio: {
|
||||
testId: this.studioStore.testId,
|
||||
suiteId: this.studioStore.suiteId,
|
||||
url: this.studioStore.url,
|
||||
},
|
||||
})
|
||||
resolve({ reporterState })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -773,14 +768,22 @@ export class EventManager {
|
||||
* This is also applicable when a user changes their spec file and hot reloads their spec, in which case we need to rebind onMessage
|
||||
* with the newly creates Cypress.primaryOriginCommunicator
|
||||
*/
|
||||
window?.top?.removeEventListener('message', crossOriginOnMessageRef, false)
|
||||
crossOriginOnMessageRef = ({ data, source }) => {
|
||||
Cypress?.primaryOriginCommunicator.onMessage({ data, source })
|
||||
try {
|
||||
window.top.removeEventListener('message', crossOriginOnMessageRef, false)
|
||||
crossOriginOnMessageRef = ({ data, source }) => {
|
||||
Cypress?.primaryOriginCommunicator.onMessage({ data, source })
|
||||
|
||||
return undefined
|
||||
return undefined
|
||||
}
|
||||
|
||||
window.top.addEventListener('message', crossOriginOnMessageRef, false)
|
||||
} catch (error) {
|
||||
// in cy-in-cy tests, window.top may not be accessible due to cross-origin restrictions
|
||||
if (error.name !== 'SecurityError') {
|
||||
// re-throw any error that's not a cross-origin error
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
window.top.addEventListener('message', crossOriginOnMessageRef, false)
|
||||
}
|
||||
|
||||
_runDriver (runState: RunState, testState: CachedTestState) {
|
||||
|
||||
@@ -318,13 +318,6 @@ async function runSpecE2E (config, spec: SpecFile) {
|
||||
specSrc: encodeURIComponent(spec.relative),
|
||||
})
|
||||
|
||||
// FIXME: BILL Determine where to call client with to force browser repaint
|
||||
/**
|
||||
* call the clientWidth to force the browser to repaint for viewport changes
|
||||
* otherwise firefox may fail when changing the viewport in between origins
|
||||
* this.refs.container.clientWidth
|
||||
*/
|
||||
|
||||
// append to document, so the iframe will execute the spec
|
||||
addIframe({
|
||||
$container,
|
||||
@@ -356,7 +349,7 @@ async function initialize () {
|
||||
|
||||
const studioStore = useStudioStore()
|
||||
|
||||
studioStore.cancel()
|
||||
studioStore.reset()
|
||||
|
||||
// TODO(lachlan): UNIFY-1318 - use GraphQL to get the viewport dimensions
|
||||
// once it is more practical to do so
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div class="border-y flex border-gray-50 w-full justify-between">
|
||||
<div
|
||||
class="border-y flex border-gray-50 w-full justify-between"
|
||||
data-cy="studio-toolbar"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="flex pr-5 pl-5 items-center">
|
||||
<span
|
||||
@@ -26,7 +29,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div
|
||||
class="flex"
|
||||
data-cy="studio-toolbar-controls"
|
||||
>
|
||||
<div class="border rounded-md flex border-gray-100 m-1">
|
||||
<Tooltip
|
||||
placement="top"
|
||||
@@ -34,6 +40,7 @@
|
||||
<button
|
||||
:class="`border-r ${controlsClassName}`"
|
||||
:disabled="studioStore.isLoading"
|
||||
data-cy="close-studio"
|
||||
@click="handleClose"
|
||||
>
|
||||
<i-cy-delete_x16 />
|
||||
@@ -49,6 +56,7 @@
|
||||
<button
|
||||
:class="`border-r ${controlsClassName}`"
|
||||
:disabled="studioStore.isLoading"
|
||||
data-cy="restart-studio"
|
||||
@click="handleRestart"
|
||||
>
|
||||
<i-cy-action-restart_x16 />
|
||||
@@ -64,6 +72,7 @@
|
||||
<button
|
||||
:class="controlsClassName"
|
||||
:disabled="studioStore.isLoading || studioStore.isEmpty"
|
||||
data-cy="copy-commands"
|
||||
@click="handleCopyCommands"
|
||||
@mouseleave="() => commandsCopied = false"
|
||||
>
|
||||
@@ -82,6 +91,7 @@
|
||||
<button
|
||||
class="rounded-md bg-indigo-500 mx-3 text-white py-2 px-3 hover:bg-indigo-400 disabled:opacity-50 disabled:pointer-events-none"
|
||||
:disabled="studioStore.isLoading || studioStore.isEmpty || studioStore.isFailed"
|
||||
data-cy="save"
|
||||
@click="handleSaveCommands"
|
||||
>
|
||||
{{ t('runner.studio.saveTestButton') }}
|
||||
|
||||
@@ -108,6 +108,7 @@ interface StudioRecorderState {
|
||||
testId?: string
|
||||
suiteId?: string
|
||||
url?: string
|
||||
_initialUrl?: string
|
||||
|
||||
fileDetails?: FileDetails
|
||||
absoluteFile?: string
|
||||
@@ -138,11 +139,13 @@ export const useStudioStore = defineStore('studioRecorder', {
|
||||
actions: {
|
||||
setTestId (testId: string) {
|
||||
this.testId = testId
|
||||
this._updateUrlParams(['testId', 'suiteId'])
|
||||
},
|
||||
|
||||
setSuiteId (suiteId: string) {
|
||||
this.suiteId = suiteId
|
||||
this.testId = undefined
|
||||
this._updateUrlParams(['testId', 'suiteId'])
|
||||
},
|
||||
|
||||
clearRunnableIds () {
|
||||
@@ -182,21 +185,19 @@ export const useStudioStore = defineStore('studioRecorder', {
|
||||
this.isFailed = true
|
||||
},
|
||||
|
||||
initialize (config, state) {
|
||||
const { studio } = state
|
||||
initialize (config) {
|
||||
const studio = this._getUrlParams()
|
||||
|
||||
if (studio) {
|
||||
if (studio.testId) {
|
||||
this.setTestId(studio.testId)
|
||||
}
|
||||
if (studio.testId) {
|
||||
this.setTestId(studio.testId)
|
||||
}
|
||||
|
||||
if (studio.suiteId) {
|
||||
this.setSuiteId(studio.suiteId)
|
||||
}
|
||||
if (studio.suiteId) {
|
||||
this.setSuiteId(studio.suiteId)
|
||||
}
|
||||
|
||||
if (studio.url) {
|
||||
this.setUrl(studio.url)
|
||||
}
|
||||
if (studio.url) {
|
||||
this._initialUrl = studio.url
|
||||
}
|
||||
|
||||
if (this.testId || this.suiteId) {
|
||||
@@ -240,8 +241,8 @@ export const useStudioStore = defineStore('studioRecorder', {
|
||||
|
||||
const autStore = useAutStore()
|
||||
|
||||
if (this.url) {
|
||||
this.visitUrl()
|
||||
if (this._initialUrl || this.url) {
|
||||
this.visitUrl(this._initialUrl)
|
||||
}
|
||||
|
||||
if (!this.url && autStore.url) {
|
||||
@@ -266,11 +267,15 @@ export const useStudioStore = defineStore('studioRecorder', {
|
||||
this._hasStarted = false
|
||||
this._currentId = 1
|
||||
this.isFailed = false
|
||||
|
||||
this._maybeResetRunnables()
|
||||
},
|
||||
|
||||
cancel () {
|
||||
this.reset()
|
||||
this.clearRunnableIds()
|
||||
this._removeUrlParams()
|
||||
this._initialUrl = undefined
|
||||
},
|
||||
|
||||
startSave () {
|
||||
@@ -283,7 +288,6 @@ export const useStudioStore = defineStore('studioRecorder', {
|
||||
|
||||
save (testName?: string) {
|
||||
this.closeSaveModal()
|
||||
this.stop()
|
||||
|
||||
assertNonNullish(this.absoluteFile, `absoluteFile should exist`)
|
||||
|
||||
@@ -303,14 +307,25 @@ export const useStudioStore = defineStore('studioRecorder', {
|
||||
visitUrl (url?: string) {
|
||||
this.setUrl(url ?? this.url)
|
||||
|
||||
getCypress().cy.visit(this.url)
|
||||
// if we're visiting a new url, update the visit url param
|
||||
if (url) {
|
||||
this._updateUrlParams(['url'])
|
||||
}
|
||||
|
||||
this.logs.push({
|
||||
id: this._getId(),
|
||||
selector: undefined,
|
||||
name: 'visit',
|
||||
message: this.url,
|
||||
getCypress().cy.visit(this.url).then(() => {
|
||||
// after visiting a new url, remove the visit url param since it shouldn't be needed anymore
|
||||
this._removeUrlParams(['url'])
|
||||
})
|
||||
|
||||
// if we're visiting a new url, add the visit log
|
||||
if (url) {
|
||||
this.logs.push({
|
||||
id: this._getId(),
|
||||
selector: undefined,
|
||||
name: 'visit',
|
||||
message: this.url,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
_recordEvent (event) {
|
||||
@@ -423,6 +438,12 @@ export const useStudioStore = defineStore('studioRecorder', {
|
||||
this._closeAssertionsMenu()
|
||||
},
|
||||
|
||||
saveSuccess () {
|
||||
this.stop()
|
||||
this._removeUrlParams()
|
||||
this._initialUrl = undefined
|
||||
},
|
||||
|
||||
saveError (err: Error) {
|
||||
return {
|
||||
id: this.testId,
|
||||
@@ -527,6 +548,71 @@ export const useStudioStore = defineStore('studioRecorder', {
|
||||
return Promise.resolve()
|
||||
},
|
||||
|
||||
_maybeResetRunnables () {
|
||||
const url = new URL(window.location.href)
|
||||
const hashParams = new URLSearchParams(url.hash)
|
||||
|
||||
// if we don't have studio params, then we can reset the runnables
|
||||
// otherwise, we need to keep the runnables since we're still in studio
|
||||
if (!hashParams.has('studio')) {
|
||||
this.clearRunnableIds()
|
||||
}
|
||||
},
|
||||
|
||||
_getUrlParams () {
|
||||
const url = new URL(window.location.href)
|
||||
const hashParams = new URLSearchParams(url.hash)
|
||||
|
||||
const testId = hashParams.get('testId')
|
||||
const suiteId = hashParams.get('suiteId')
|
||||
const visitUrl = hashParams.get('url')
|
||||
|
||||
return { testId, suiteId, url: visitUrl }
|
||||
},
|
||||
|
||||
_updateUrlParams (filter: string[] = ['testId', 'suiteId', 'url']) {
|
||||
// if we don't have studio params, we don't need to update them
|
||||
if (!this.testId && !this.suiteId && !this.url) return
|
||||
|
||||
const url = new URL(window.location.href)
|
||||
const hashParams = new URLSearchParams(url.hash)
|
||||
|
||||
// if we have studio params, we need to remove them before adding them back
|
||||
this._removeUrlParams(filter)
|
||||
|
||||
// set the studio params
|
||||
hashParams.set('studio', '')
|
||||
filter.forEach((param) => {
|
||||
if (this[param]) hashParams.set(param, this[param])
|
||||
})
|
||||
|
||||
// update the url
|
||||
url.hash = decodeURIComponent(hashParams.toString())
|
||||
window.history.replaceState({}, '', url.toString())
|
||||
},
|
||||
|
||||
_removeUrlParams (filter: string[] = ['testId', 'suiteId', 'url']) {
|
||||
const url = new URL(window.location.href)
|
||||
const hashParams = new URLSearchParams(url.hash)
|
||||
|
||||
// if we don't have studio params, we don't need to remove them
|
||||
if (!hashParams.has('studio')) return
|
||||
|
||||
// remove the studio params
|
||||
filter.forEach((param) => {
|
||||
hashParams.delete(param)
|
||||
})
|
||||
|
||||
// if the filter includes all the items, we can also remove the studio param
|
||||
if (filter.length === 3) {
|
||||
hashParams.delete('studio')
|
||||
}
|
||||
|
||||
// update the url
|
||||
url.hash = decodeURIComponent(hashParams.toString())
|
||||
window.history.replaceState({}, '', url.toString())
|
||||
},
|
||||
|
||||
_trustEvent (event) {
|
||||
// only capture events sent by the actual user
|
||||
// but disable the check if we're in a test
|
||||
|
||||
@@ -33,8 +33,8 @@ const makePathsAbsoluteToDoc = $utils.memoize((styles, doc) => {
|
||||
if (!_.isString(styles)) return styles
|
||||
|
||||
return styles.replace(anyUrlInCssRe, (_1, _2, filePath) => {
|
||||
//// the href getter will always resolve an absolute path taking into
|
||||
//// account things like the current URL and the <base> tag
|
||||
// the href getter will always resolve an absolute path taking into
|
||||
// account things like the current URL and the <base> tag
|
||||
const a = doc.createElement('a')
|
||||
|
||||
a.href = filePath
|
||||
@@ -59,9 +59,9 @@ const makePathsAbsoluteToStylesheet = $utils.memoize((styles, href) => {
|
||||
}, makePathsAbsoluteToStylesheetCache)
|
||||
|
||||
const getExternalCssContents = (href, stylesheet) => {
|
||||
//// some browsers may throw a SecurityError if the stylesheet is cross-origin
|
||||
//// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet#Notes
|
||||
//// for others, it will just be null
|
||||
// some browsers may throw a SecurityError if the stylesheet is cross-origin
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet#Notes
|
||||
// for others, it will just be null
|
||||
try {
|
||||
const rules = stylesheet.rules || stylesheet.cssRules
|
||||
|
||||
@@ -96,14 +96,14 @@ export const create = ($$, state) => {
|
||||
const cssHrefToModifiedMap = new LimitedMap()
|
||||
let newWindow = false
|
||||
|
||||
//// we invalidate the cache when css is modified by javascript
|
||||
// we invalidate the cache when css is modified by javascript
|
||||
const onCssModified = (href) => {
|
||||
cssHrefToModifiedMap.set(href, { modified: true })
|
||||
}
|
||||
|
||||
//// the lifecycle of a stylesheet is the lifecycle of the window
|
||||
//// so track this to know when to re-evaluate the cache in case
|
||||
//// of css being modified by javascript
|
||||
// the lifecycle of a stylesheet is the lifecycle of the window
|
||||
// so track this to know when to re-evaluate the cache in case
|
||||
// of css being modified by javascript
|
||||
const onBeforeWindowLoad = () => {
|
||||
newWindow = true
|
||||
}
|
||||
@@ -112,8 +112,8 @@ export const create = ($$, state) => {
|
||||
const hrefModified = cssHrefToModifiedMap.get(href) || {}
|
||||
const existing = cssHrefToIdMap.get(href)
|
||||
|
||||
//// if we've loaded a new window and the css was invalidated due to javascript
|
||||
//// we need to re-evaluate since this time around javascript might not change the css
|
||||
// if we've loaded a new window and the css was invalidated due to javascript
|
||||
// we need to re-evaluate since this time around javascript might not change the css
|
||||
if (existing && !hrefModified.modified && !(newWindow && hrefModified.modifiedLast)) {
|
||||
return existing
|
||||
}
|
||||
@@ -125,16 +125,16 @@ export const create = ($$, state) => {
|
||||
}
|
||||
|
||||
const hashedCssContents = md5(cssContents)
|
||||
//// if we already have these css contents stored, don't store them again
|
||||
// if we already have these css contents stored, don't store them again
|
||||
const existingId = cssHashedContentsToIdMap.get(hashedCssContents)
|
||||
|
||||
//// id just needs to be a new object reference
|
||||
//// we add the href for debuggability
|
||||
// id just needs to be a new object reference
|
||||
// we add the href for debuggability
|
||||
const id = existingId || { hrefId: href }
|
||||
|
||||
cssHrefToIdMap.set(href, id)
|
||||
|
||||
//// if we already have these css contents stored, don't store them again
|
||||
// if we already have these css contents stored, don't store them again
|
||||
if (!existingId) {
|
||||
cssHashedContentsToIdMap.set(hashedCssContents, id)
|
||||
cssIdToContentsMap.set(id, cssContents)
|
||||
@@ -158,19 +158,19 @@ export const create = ($$, state) => {
|
||||
styles = _.filter(styles, isScreenStylesheet)
|
||||
|
||||
return _.map(styles, (stylesheet) => {
|
||||
//// in cases where we can get the CSS as a string, make the paths
|
||||
//// absolute so that when they're restored by appending them to the page
|
||||
//// in <style> tags, background images and fonts still properly load
|
||||
// in cases where we can get the CSS as a string, make the paths
|
||||
// absolute so that when they're restored by appending them to the page
|
||||
// in <style> tags, background images and fonts still properly load
|
||||
const href = stylesheet.href
|
||||
|
||||
//// if there's an href, it's a link tag
|
||||
//// return the CSS rules as a string, or, if cross-origin,
|
||||
//// a reference to the stylesheet's href
|
||||
// if there's an href, it's a link tag
|
||||
// return the CSS rules as a string, or, if cross-origin,
|
||||
// a reference to the stylesheet's href
|
||||
if (href) {
|
||||
return getStyleId(href, stylesheets[href]) || { href }
|
||||
}
|
||||
|
||||
//// otherwise, it's a style tag, and we can just grab its content
|
||||
// otherwise, it's a style tag, and we can just grab its content
|
||||
const cssContents = getInlineCssContents(stylesheet, $$)
|
||||
|
||||
return makePathsAbsoluteToDoc(cssContents, doc)
|
||||
@@ -186,7 +186,7 @@ export const create = ($$, state) => {
|
||||
bodyStyleIds: getStyleIdsFor(doc, $$, stylesheets, 'body'),
|
||||
}
|
||||
|
||||
//// after getting the all the styles on the page, it's no longer a new window
|
||||
// after getting the all the styles on the page, it's no longer a new window
|
||||
newWindow = false
|
||||
|
||||
return styleIds
|
||||
|
||||
@@ -11,7 +11,15 @@ const fetchScript = (scriptWindow, script) => {
|
||||
}
|
||||
|
||||
const extractSourceMap = ([script, contents]) => {
|
||||
script.fullyQualifiedUrl = `${window.top!.location.origin}${script.relativeUrl}`.replace(/ /g, '%20')
|
||||
try {
|
||||
script.fullyQualifiedUrl = `${window.top!.location.origin}${script.relativeUrl}`.replace(/ /g, '%20')
|
||||
} catch (error) {
|
||||
// in cy-in-cy tests, window.top may not be accessible due to cross-origin restrictions
|
||||
if (error.name !== 'SecurityError') {
|
||||
// re-throw any error that's not a cross-origin error
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const sourceMap = $sourceMapUtils.extractSourceMap(contents)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ type MessageLines = [string[], string[]] & {messageEnded?: boolean}
|
||||
|
||||
// returns tuple of [message, stack]
|
||||
export const splitStack = (stack: string) => {
|
||||
const lines = stack.split('\n')
|
||||
const lines = stack?.split('\n')
|
||||
|
||||
return _.reduce(lines, (memo, line) => {
|
||||
if (memo.messageEnded || stackLineRegex.test(line)) {
|
||||
|
||||
@@ -171,13 +171,7 @@ export const createCommonRoutes = ({
|
||||
res.sendFile(file, { etag: false })
|
||||
})
|
||||
|
||||
// TODO: The below route is not technically correct for cypress in cypress tests.
|
||||
// We should be using 'config.namespace' to provide the namespace instead of hard coding __cypress, however,
|
||||
// In the runner when we create the spec bridge we have no knowledge of the namespace used by the server so
|
||||
// we create a spec bridge for the namespace of the server specified in the config, but that server hasn't been created.
|
||||
// To fix this I think we need to find a way to listen in the cypress in cypress server for routes from the server the
|
||||
// cypress instance thinks should exist, but that's outside the current scope.
|
||||
router.get('/__cypress/spec-bridge-iframes', async (req, res) => {
|
||||
router.get(`/${config.namespace}/spec-bridge-iframes`, async (req, res) => {
|
||||
debug('handling cross-origin iframe for domain: %s', req.hostname)
|
||||
|
||||
// Chrome plans to make document.domain immutable in Chrome 109, with the default value
|
||||
|
||||
@@ -87,6 +87,16 @@ const _forceProxyMiddleware = function (clientRoute, namespace = '__cypress') {
|
||||
return function (req, res, next) {
|
||||
const trimmedUrl = _.trimEnd(req.proxiedUrl, '/')
|
||||
|
||||
// if this request is a non-proxied cy-in-cy request,
|
||||
// we need to update the proxiedUrl and allow it to pass through
|
||||
if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF && _isNonProxiedRequest(req)) {
|
||||
const referrerUrl = new URL(req.headers.referer)
|
||||
|
||||
req.proxiedUrl = `${referrerUrl.origin}${req.proxiedUrl}`
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
if (_isNonProxiedRequest(req) && !ALLOWED_PROXY_BYPASS_URLS.includes(trimmedUrl) && (trimmedUrl !== trimmedClientRoute)) {
|
||||
// this request is non-proxied and non-allowed, redirect to the runner error page
|
||||
return res.redirect(clientRoute)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReporterRunState, StudioRecorderState } from './reporter'
|
||||
import type { ReporterRunState } from './reporter'
|
||||
|
||||
interface MochaRunnerState {
|
||||
startTime?: number
|
||||
@@ -12,7 +12,6 @@ interface MochaRunnerState {
|
||||
}
|
||||
|
||||
export type RunState = MochaRunnerState & ReporterRunState & {
|
||||
studio?: StudioRecorderState
|
||||
isSpecsListOpen?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
export interface StudioRecorderState {
|
||||
suiteId?: string
|
||||
testId?: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
export interface ReporterRunState {
|
||||
autoScrollingEnabled?: boolean
|
||||
scrollTop?: number
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
describe('studio functionality', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', 'http://foobar.com:4455/cypress/e2e/index.html', {
|
||||
statusCode: 200,
|
||||
body: '<html><body><h1>hello world</h1></body></html>',
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('visits a basic html page', () => {
|
||||
cy.visit('cypress/e2e/index.html')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
describe('studio functionality', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('cypress/e2e/index.html')
|
||||
})
|
||||
|
||||
it('visits a basic html page', () => {
|
||||
cy.get('h1').should('have.text', 'Hello, Studio!')
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
it('visits a basic html page', () => {
|
||||
cy.visit('cypress/e2e/index.html')
|
||||
describe('studio functionality', () => {
|
||||
it('visits a basic html page', () => {
|
||||
cy.visit('cypress/e2e/index.html')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// REPLACE THIS COMMENT FOR HOT RELOAD
|
||||
describe('simple origin', () => {
|
||||
it('passes', () => {
|
||||
cy.origin('http://foobar:4455', () => {
|
||||
cy.origin('http://foobar.com:4455', () => {
|
||||
cy.log('log me once')
|
||||
cy.log('log me twice')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user