Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ _Released 12/2/2025 (PENDING)_

- Improved performance when viewing command snapshots in the Command Log. Element highlighting is now significantly faster, especially when highlighting multiple elements or complex pages. This is achieved by reducing redundant style calculations and batching DOM operations to minimize browser reflows. Addressed in [#32951](https://github.com/cypress-io/cypress/pull/32951).

**Bugfixes:**

- Fixed an issue where a <script> element before <html> was not injected properly. Addressed in [#32982](https://github.com/cypress-io/cypress/pull/32982).

## 15.7.0

_Released 11/19/2025_
Expand Down
33 changes: 29 additions & 4 deletions packages/proxy/lib/http/util/rewriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type InjectionOpts = {
}

const doctypeRe = /<\!doctype.*?>/i
const scriptRe = /<script.*?>/i
const headRe = /<head(?!er).*?>/i
const bodyRe = /<body.*?>/i
const htmlRe = /<html.*?>/i
Expand Down Expand Up @@ -78,8 +79,27 @@ const insertAfter = (originalString, match, stringToInsert) => {
return `${originalString.slice(0, index)} ${stringToInsert}${originalString.slice(index)}`
}

export async function html (html: string, opts: SecurityOpts & InjectionOpts) {
const htmlToInject = await Promise.resolve(getHtmlToInject(opts))
const isScriptTopLevel = (scriptMatch, htmlMatch) => {
if (!scriptMatch) {
return false
}

if (!htmlMatch) {
return true
}

const scriptIndex = scriptMatch.index
const htmlIndex = htmlMatch.index

if (scriptIndex === undefined || htmlIndex === undefined) {
return false
}

return scriptIndex < htmlIndex
}

export async function html (html: string, opts: SecurityOpts & InjectionOpts, mockedHtmlToInject: string = '') {
const htmlToInject = mockedHtmlToInject || await Promise.resolve(getHtmlToInject(opts))

// strip clickjacking and framebusting
// from the HTML if we've been told to
Expand All @@ -93,6 +113,13 @@ export async function html (html: string, opts: SecurityOpts & InjectionOpts) {

// TODO: move this into regex-rewriting and have ast-rewriting handle this in its own way

const scriptMatch = html.match(scriptRe)
const htmlMatch = html.match(htmlRe)

if (isScriptTopLevel(scriptMatch, htmlMatch)) {
return insertBefore(html, scriptMatch, htmlToInject)
}

const headMatch = html.match(headRe)

if (headMatch) {
Expand All @@ -105,8 +132,6 @@ export async function html (html: string, opts: SecurityOpts & InjectionOpts) {
return insertBefore(html, bodyMatch, `<head> ${htmlToInject} </head>`)
}

const htmlMatch = html.match(htmlRe)

if (htmlMatch) {
return insertAfter(html, htmlMatch, `<head> ${htmlToInject} </head>`)
}
Expand Down
43 changes: 43 additions & 0 deletions packages/proxy/test/unit/http/util/rewriter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest'
import { html } from '../../../../lib/http/util/rewriter'

const injected = `<script>(() => { TEST })()</script>`

describe('http/util/rewriter', () => {
describe('.top-level tests', () => {
it('just a script tag', async () => {
expect(await html('<script>Original HTML</script>', {} as any, injected))
.toEqual(`${injected} <script>Original HTML</script>`)
})

it('script tag before a html tag', async () => {
expect(await html('<script>Original Script</script><html>Test HTML</html>', {} as any, injected))
.toEqual(`${injected} <script>Original Script</script><html>Test HTML</html>`)
})

it('script tag after a html tag', async () => {
expect(await html('<html>Test HTML</html><script>Original Script</script>', {} as any, injected))
.toEqual(`<html> <head> ${injected} </head>Test HTML</html><script>Original Script</script>`)
})

it('script tag inside head tag with no body tag', async () => {
expect(await html('<html><head><script>Original Script</script></head></html>', {} as any, injected))
.toEqual(`<html><head> ${injected}<script>Original Script</script></head></html>`)
})

it('script tag inside head tag with a body tag', async () => {
expect(await html('<html><head><script>Original Script</script></head><body>Example Body</body></html>', {} as any, injected))
.toEqual(`<html><head> ${injected}<script>Original Script</script></head><body>Example Body</body></html>`)
})

it('script tag inside body tag with no head tag', async () => {
expect(await html('<html><body><script>Original Script</script></body></html>', {} as any, injected))
.toEqual(`<html><head> ${injected} </head> <body><script>Original Script</script></body></html>`)
})

it('script tag inside body tag with a head tag', async () => {
expect(await html('<html><head>Original Head</head><body><script>Original Script</script></body></html>', {} as any, injected))
.toEqual(`<html><head> ${injected}Original Head</head><body><script>Original Script</script></body></html>`)
})
})
})