Skip to content
Merged
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
16 changes: 13 additions & 3 deletions lib/helper/CDPBrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import Helper from '@codeceptjs/helper'
import CDPConnection from './extras/CDPConnection.js'
import installCodeceptClient from './clientscripts/cdpBrowserClient.js'
import xpathPolyfillSource from './clientscripts/xpathPolyfill.js'
import layoutProbe from './clientscripts/layoutProbe.js'
import Locator from '../locator.js'
import output from '../output.js'
import store from '../store.js'
import { xpathLocator, normalizePath, resolveUrl, toCamelCase, convertCssPropertiesToCamelCase, normalizeSpacesInString, fileExists, base64EncodeFile, getMimeType, isFile, isWindows } from '../utils.js'
import ElementNotFound from './errors/ElementNotFound.js'
Expand Down Expand Up @@ -595,8 +597,8 @@ class CDPBrowser extends Helper {

async _probeCapabilities() {
if (this.capabilities.layout === null) {
const display = await this._evaluate(`getComputedStyle(document.documentElement).display`)
this.capabilities.layout = display === '' ? 'none' : 'real'
const display = await this._evaluate(`(${layoutProbe.toString()})()`)
this.capabilities.layout = display === 'none' ? 'real' : 'none'
}
if (this.capabilities.screenshot === null) {
this.capabilities.screenshot = this.capabilities.layout === 'real'
Expand All @@ -610,6 +612,13 @@ class CDPBrowser extends Helper {
if (this.capabilities.innerText === null) {
await this._needsVisibleTextFallback()
}
if (!this._engineReported) {
this._engineReported = true
let engine = 'render (layout and screenshots)'
if (this.capabilities.layout === 'none') engine = 'no-render (DOM only: no layout, visibility checks or screenshots)'
else if (!this.capabilities.screenshot) engine = 'layout without screenshots'
output.print(`${this.constructor.name} engine: ${engine}`)
}
}

async _run(candidates, action, payload, context = null) {
Expand Down Expand Up @@ -807,7 +816,8 @@ class CDPBrowser extends Helper {
const useWalker = await this._needsVisibleTextFallback()
const res = await this._runTextCheck(text, { ignoreCase, useWalker })
if (negate ? !res.found : res.found) return
let source = await this._textSource(null)
let source = res.text
if (source === undefined) source = await this._textSource(null)
let needle = text
if (ignoreCase) {
needle = needle.toLowerCase()
Expand Down
2 changes: 1 addition & 1 deletion lib/helper/clientscripts/cdpBrowserClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default function installCodeceptClient(xpathNeedsPolyfill) {
const hayCmp = opts.ignoreCase ? hay.toLowerCase() : hay
const needleCmp = opts.ignoreCase ? needleNorm.toLowerCase() : needleNorm
const idx = hayCmp.indexOf(needleCmp)
if (idx === -1) return { found: false, snippet: null }
if (idx === -1) return { found: false, snippet: null, text: hay }
const from = Math.max(0, idx - 60)
return { found: true, snippet: hay.slice(from, idx + needleNorm.length + 60) }
}
Expand Down
12 changes: 12 additions & 0 deletions lib/helper/clientscripts/layoutProbe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default function layoutProbe() {
const style = document.createElement('style')
style.textContent = '.__codecept-layout-probe{display:none}'
document.documentElement.appendChild(style)
const probe = document.createElement('div')
probe.className = '__codecept-layout-probe'
;(document.body || document.documentElement).appendChild(probe)
const display = getComputedStyle(probe).display
style.remove()
probe.remove()
return display
}
12 changes: 10 additions & 2 deletions lib/helper/extras/CDPConnection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { WebSocket } from 'ws'
import debugModule from 'debug'

const debug = debugModule('codeceptjs:cdp')

class CDPConnection {
constructor(endpoint, options = {}) {
Expand All @@ -9,6 +12,7 @@ class CDPConnection {
this.lastId = 0
this.pending = new Map()
this.listeners = new Map()
this.totals = {}
}

async connect() {
Expand Down Expand Up @@ -49,7 +53,7 @@ class CDPConnection {
this.pending.delete(id)
reject(new Error(`CDP command ${method} timed out after ${this.timeout}ms`))
}, this.timeout)
this.pending.set(id, { resolve, reject, timer })
this.pending.set(id, { resolve, reject, timer, method, started: Date.now() })
this.ws.send(JSON.stringify(message))
})
}
Expand All @@ -61,9 +65,12 @@ class CDPConnection {

_onMessage(msg) {
if (msg.id && this.pending.has(msg.id)) {
const { resolve, reject, timer } = this.pending.get(msg.id)
const { resolve, reject, timer, method, started } = this.pending.get(msg.id)
clearTimeout(timer)
this.pending.delete(msg.id)
const ms = Date.now() - started
this.totals[method] = (this.totals[method] || 0) + ms
debug(`${method} ${ms}ms`)
if (msg.error) reject(new Error(msg.error.message))
else resolve(msg.result)
return
Expand All @@ -80,6 +87,7 @@ class CDPConnection {

async close() {
if (!this.ws) return
debug('total time per method', this.totals)
await new Promise(resolve => {
if (this.ws.readyState === WebSocket.CLOSED) return resolve()
this.ws.once('close', resolve)
Expand Down
6 changes: 4 additions & 2 deletions test/helper/webapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2618,7 +2618,8 @@ export function tests() {
})

describe('role locators', () => {
it('should locate elements by role', async () => {
it('should locate elements by role', async function () {
if (I.capabilities?.layout === 'none') this.skip() // visibility requires a real layout engine
await I.amOnPage('/form/role_elements')

// Test basic role locators
Expand All @@ -2634,7 +2635,8 @@ export function tests() {
await I.seeNumberOfVisibleElements({ role: 'checkbox' }, 2)
})

it('should locate elements by role with text filter', async () => {
it('should locate elements by role with text filter', async function () {
if (I.capabilities?.layout === 'none') this.skip() // visibility requires a real layout engine
await I.amOnPage('/form/role_elements')

// Test role with text (exact match)
Expand Down
Loading