codeceptjs
Version:
Supercharged End 2 End Testing Framework for NodeJS
328 lines (300 loc) • 9.22 kB
JavaScript
const assert = require('assert')
/**
* Unified WebElement class that wraps native element instances from different helpers
* and provides a consistent API across all supported helpers (Playwright, WebDriver, Puppeteer).
*/
class WebElement {
constructor(element, helper) {
this.element = element
this.helper = helper
this.helperType = this._detectHelperType(helper)
}
_detectHelperType(helper) {
if (!helper) return 'unknown'
const className = helper.constructor.name
if (className === 'Playwright') return 'playwright'
if (className === 'WebDriver') return 'webdriver'
if (className === 'Puppeteer') return 'puppeteer'
return 'unknown'
}
/**
* Get the native element instance
* @returns {ElementHandle|WebElement|ElementHandle} Native element
*/
getNativeElement() {
return this.element
}
/**
* Get the helper instance
* @returns {Helper} Helper instance
*/
getHelper() {
return this.helper
}
/**
* Get text content of the element
* @returns {Promise<string>} Element text content
*/
async getText() {
switch (this.helperType) {
case 'playwright':
return this.element.textContent()
case 'webdriver':
return this.element.getText()
case 'puppeteer':
return this.element.evaluate(el => el.textContent)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Get attribute value of the element
* @param {string} name Attribute name
* @returns {Promise<string|null>} Attribute value
*/
async getAttribute(name) {
switch (this.helperType) {
case 'playwright':
return this.element.getAttribute(name)
case 'webdriver':
return this.element.getAttribute(name)
case 'puppeteer':
return this.element.evaluate((el, attrName) => el.getAttribute(attrName), name)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Get property value of the element
* @param {string} name Property name
* @returns {Promise<any>} Property value
*/
async getProperty(name) {
switch (this.helperType) {
case 'playwright':
return this.element.evaluate((el, propName) => el[propName], name)
case 'webdriver':
return this.element.getProperty(name)
case 'puppeteer':
return this.element.evaluate((el, propName) => el[propName], name)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Get innerHTML of the element
* @returns {Promise<string>} Element innerHTML
*/
async getInnerHTML() {
switch (this.helperType) {
case 'playwright':
return this.element.innerHTML()
case 'webdriver':
return this.element.getProperty('innerHTML')
case 'puppeteer':
return this.element.evaluate(el => el.innerHTML)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Get value of the element (for input elements)
* @returns {Promise<string>} Element value
*/
async getValue() {
switch (this.helperType) {
case 'playwright':
return this.element.inputValue()
case 'webdriver':
return this.element.getValue()
case 'puppeteer':
return this.element.evaluate(el => el.value)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Check if element is visible
* @returns {Promise<boolean>} True if element is visible
*/
async isVisible() {
switch (this.helperType) {
case 'playwright':
return this.element.isVisible()
case 'webdriver':
return this.element.isDisplayed()
case 'puppeteer':
return this.element.evaluate(el => {
const style = window.getComputedStyle(el)
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
})
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Check if element is enabled
* @returns {Promise<boolean>} True if element is enabled
*/
async isEnabled() {
switch (this.helperType) {
case 'playwright':
return this.element.isEnabled()
case 'webdriver':
return this.element.isEnabled()
case 'puppeteer':
return this.element.evaluate(el => !el.disabled)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Check if element exists in DOM
* @returns {Promise<boolean>} True if element exists
*/
async exists() {
try {
switch (this.helperType) {
case 'playwright':
// For Playwright, if we have the element, it exists
return await this.element.evaluate(el => !!el)
case 'webdriver':
// For WebDriver, if we have the element, it exists
return true
case 'puppeteer':
// For Puppeteer, if we have the element, it exists
return await this.element.evaluate(el => !!el)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
} catch (e) {
return false
}
}
/**
* Get bounding box of the element
* @returns {Promise<Object>} Bounding box with x, y, width, height properties
*/
async getBoundingBox() {
switch (this.helperType) {
case 'playwright':
return this.element.boundingBox()
case 'webdriver':
const rect = await this.element.getRect()
return {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
}
case 'puppeteer':
return this.element.boundingBox()
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Click the element
* @param {Object} options Click options
* @returns {Promise<void>}
*/
async click(options = {}) {
switch (this.helperType) {
case 'playwright':
return this.element.click(options)
case 'webdriver':
return this.element.click()
case 'puppeteer':
return this.element.click(options)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Type text into the element
* @param {string} text Text to type
* @param {Object} options Type options
* @returns {Promise<void>}
*/
async type(text, options = {}) {
switch (this.helperType) {
case 'playwright':
return this.element.type(text, options)
case 'webdriver':
return this.element.setValue(text)
case 'puppeteer':
return this.element.type(text, options)
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
}
/**
* Find first child element matching the locator
* @param {string|Object} locator Element locator
* @returns {Promise<WebElement|null>} WebElement instance or null if not found
*/
async $(locator) {
let childElement
switch (this.helperType) {
case 'playwright':
childElement = await this.element.$(this._normalizeLocator(locator))
break
case 'webdriver':
try {
childElement = await this.element.$(this._normalizeLocator(locator))
} catch (e) {
return null
}
break
case 'puppeteer':
childElement = await this.element.$(this._normalizeLocator(locator))
break
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
return childElement ? new WebElement(childElement, this.helper) : null
}
/**
* Find all child elements matching the locator
* @param {string|Object} locator Element locator
* @returns {Promise<WebElement[]>} Array of WebElement instances
*/
async $$(locator) {
let childElements
switch (this.helperType) {
case 'playwright':
childElements = await this.element.$$(this._normalizeLocator(locator))
break
case 'webdriver':
childElements = await this.element.$$(this._normalizeLocator(locator))
break
case 'puppeteer':
childElements = await this.element.$$(this._normalizeLocator(locator))
break
default:
throw new Error(`Unsupported helper type: ${this.helperType}`)
}
return childElements.map(el => new WebElement(el, this.helper))
}
/**
* Normalize locator for element search
* @param {string|Object} locator Locator to normalize
* @returns {string} Normalized CSS selector
* @private
*/
_normalizeLocator(locator) {
if (typeof locator === 'string') {
return locator
}
if (typeof locator === 'object') {
// Handle CodeceptJS locator objects
if (locator.css) return locator.css
if (locator.xpath) return locator.xpath
if (locator.id) return `#${locator.id}`
if (locator.name) return `[name="${locator.name}"]`
if (locator.className) return `.${locator.className}`
}
return locator.toString()
}
}
module.exports = WebElement