import path from 'node:path'
import { expect, describe, it, afterEach, vi } from 'vitest'
import { remote } from '../../../src/index.js'
import { sanitizeHTML } from '../../../src/commands/element/getHTML.js'
vi.mock('fetch')
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
describe('getHTML test', () => {
it('should allow get html of an element', async () => {
const browser = await remote({
baseUrl: 'http://foobar.com',
capabilities: {
browserName: 'foobar'
}
})
const elem = await browser.$('#foo') as any
elem.elementId = {
outerHTML: '<some>outer html</some>',
innerHTML: 'some inner html'
}
let result = await elem.getHTML()
expect(vi.mocked(fetch).mock.calls[2][0].pathname)
.toBe('/session/foobar-123/execute/sync')
expect(result).toBe('<some>outer html</some>')
result = await elem.getHTML({ includeSelectorTag: false })
expect(result).toBe('some inner html')
})
describe('sanitizeHTML', () => {
it('should remove simple HTML comments by default', () => {
const html = '<!-- comment -->'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('')
})
it('should remove comments with content around them', () => {
const html = 'Hello <!-- comment --> World'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('Hello World')
})
it('should preserve comments when removeCommentNodes is false', () => {
const html = 'Hello <!-- comment --> World'
const result = sanitizeHTML(html, { removeCommentNodes: false, prettify: false })
expect(result).toBe('Hello <!-- comment --> World')
})
it('should remove multiple comments', () => {
const html = '<!-- start -->Text<!-- middle -->More<!-- end -->'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('TextMore')
})
it('should handle framework-specific comments (e.g., Lit)', () => {
const html = 'Content<!--?lit$206212805$--><!--?lit$206212805$-->After'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('ContentAfter')
})
it('should handle multiline comments', () => {
const html = `Text<!--
multiline
comment
-->End`
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('TextEnd')
})
it('should handle multiline comments with various content', () => {
const html = `<div>
<!-- This is a comment
that spans multiple lines
and contains various content:
- List items
- Special chars: <>&'"
- Even code: function() { return true; }
-->
<p>Content</p>
</div>`
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).not.toContain('<!--')
expect(result).toContain('<p>Content</p>')
expect(result).not.toContain('List items')
expect(result).not.toContain('Special chars')
})
it('should handle multiple multiline comments', () => {
const html = `Start
<!--
First multiline
comment block
-->
Middle
<!--
Second multiline
comment block
-->
End`
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result.replace(/\s+/g, ' ').trim()).toBe('Start Middle End')
})
it('should handle multiline comments with nested HTML-like content', () => {
const html = `<div>
<!--
<div class="commented-out">
<p>This HTML is commented out</p>
<script>console.log('also commented')</script>
</div>
-->
<p>This is visible</p>
</div>`
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).not.toContain('commented-out')
expect(result).not.toContain('console.log')
expect(result).toContain('<p>This is visible</p>')
})
it('should handle Windows-style line endings in multiline comments', () => {
const html = 'Before<!--\r\nWindows\r\nstyle\r\ncomment\r\n-->After'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('BeforeAfter')
})
it('should handle mixed line endings in multiline comments', () => {
const html = 'Text<!--\nUnix line\r\nWindows line\rOld Mac line-->End'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('TextEnd')
})
it('should handle multiline comments in Cheerio mode', async () => {
const { load } = await import('cheerio')
const html = `
<html>
<body>
<!--
This is a multiline comment
in the document
-->
<div>
Content
<!--
Another multiline
comment inside div
-->
</div>
</body>
</html>
`
const $ = load(html)
const result = sanitizeHTML($, { removeCommentNodes: true, prettify: false })
expect(result).not.toContain('<!--')
expect(result).not.toContain('multiline comment')
expect(result).toContain('<div>')
expect(result).toContain('Content')
})
it('should handle comments with special characters', () => {
const html = 'Before<!-- comment with <tags> & special chars -->After'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('BeforeAfter')
})
it('should handle empty comments', () => {
const html = 'Text<!---->More'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('TextMore')
})
it('should not be vulnerable to ReDoS attacks', () => {
const maliciousInput = '<!--' + 'a'.repeat(10000) + '-->' + '<!--' + 'b'.repeat(10000) + '-->'
const startTime = Date.now()
const result = sanitizeHTML(maliciousInput, { removeCommentNodes: true, prettify: false })
const executionTime = Date.now() - startTime
expect(executionTime).toBeLessThan(50)
expect(result).not.toContain('<!--')
expect(result).toBe('')
})
it('should handle nested comment-like structures', () => {
const html = '<!-- outer <!-- inner --> still in comment --> Text'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe(' still in comment --> Text')
})
it('should handle incomplete comments', () => {
const html = 'Text <!--incomplete comment'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('Text <!--incomplete comment')
})
it('should remove comments in complex HTML when using Cheerio', async () => {
const { load } = await import('cheerio')
const complexHtml = `
<div>
<!-- Comment 1 -->
<p>Text <!-- inline comment --> content</p>
<!-- Comment 2 -->
<span>More<!--comment-->text</span>
</div>
`
const $ = load(complexHtml)
const result = sanitizeHTML($, { removeCommentNodes: true, prettify: false })
expect(result).not.toContain('<!--')
expect(result).toContain('<p>Text content</p>')
expect(result).toContain('<span>Moretext</span>')
})
it('should exclude elements when specified', async () => {
const { load } = await import('cheerio')
const html = `
<div>
<style>body { color: red; }</style>
<p>Keep this</p>
<script>console.log('remove')</script>
</div>
`
const $ = load(html)
const result = sanitizeHTML($, {
excludeElements: ['style', 'script'],
prettify: false
})
expect(result).not.toContain('<style>')
expect(result).not.toContain('<script>')
expect(result).toContain('<p>Keep this</p>')
})
it('should remove comments and exclude elements together', async () => {
const { load } = await import('cheerio')
const html = `
<div>
<!-- Remove this comment -->
<style>/* CSS comment */</style>
<p>Keep this<!-- but not this --></p>
</div>
`
const $ = load(html)
const result = sanitizeHTML($, {
removeCommentNodes: true,
excludeElements: ['style'],
prettify: false
})
expect(result).not.toContain('<!--')
expect(result).not.toContain('<style>')
expect(result).toContain('<p>Keep this</p>')
})
it('should handle comments at the beginning and end', () => {
const html = '<!--start-->Middle<!--end-->'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('Middle')
})
it('should handle consecutive comments', () => {
const html = 'Text<!-- comment1 --><!-- comment2 --><!-- comment3 -->End'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('TextEnd')
})
it('should handle HTML entities in comments', () => {
const html = 'Text<!-- comment with <entities> -->End'
const result = sanitizeHTML(html, { removeCommentNodes: true, prettify: false })
expect(result).toBe('TextEnd')
})
})
afterEach(() => {
vi.mocked(fetch).mockClear()
})
})