import cssShorthandProps from 'css-shorthand-properties'
import { getBrowserObject } from '@wdio/utils'
import { parseCSS } from '../../utils/index.js'
type PseudoElement = '::before' | '::after'
/**
*
* Get a css property from a DOM-element selected by given selector. The return value
* is formatted to be testable. Colors gets parsed via [rgb2hex](https://www.npmjs.org/package/rgb2hex)
* and all other properties get parsed via [css-value](https://www.npmjs.org/package/css-value).
*
* :::info
*
* Note that shorthand CSS properties (e.g. `background`, `font`, `border`, `margin`,
* `padding`, `list-style`, `outline`, `pause`, `cue`) will be expanded to fetch all longhand
* properties resulting in multiple WebDriver calls. If you are interested in a specific
* longhand property it is recommended to query for that instead.
*
* :::
*
*
:example.html
:getCSSProperty.js
it('should demonstrate the getCSSProperty command', async () => {
const elem = await $('#myLabel')
const color = await elem.getCSSProperty('color')
console.log(color)
// outputs the following:
// {
// property: 'color',
// value: 'rgba(0, 136, 204, 1)',
// parsed: {
// hex: '#0088cc',
// alpha: 1,
// type: 'color',
// rgba: 'rgba(0, 136, 204, 1)'
// }
// }
const font = await elem.getCSSProperty('font-family')
console.log(font)
// outputs the following:
// {
// property: 'font-family',
// value: 'helvetica',
// parsed: {
// value: [ 'helvetica', 'arial', 'freesans', 'clean', 'sans-serif' ],
// type: 'font',
// string: 'helvetica, arial, freesans, clean, sans-serif'
// }
// }
var width = await elem.getCSSProperty('width', '::before')
console.log(width)
// outputs the following:
// {
// property: 'width',
// value: '100px',
// parsed: {
// type: 'number',
// string: '100px',
// unit: 'px',
// value: 100
// }
// }
})
*
* @alias element.getCSSProperty
* @param {string} cssProperty css property name
* @param {PseudoElement} pseudoElement css pseudo element
* @return {CSSProperty} The specified css of the element
*
*/
export async function getCSSProperty (
this: WebdriverIO.Element,
cssProperty: string,
pseudoElement?: PseudoElement,
) {
const getCSSProperty = cssShorthandProps.isShorthand(cssProperty)
? getShorthandPropertyCSSValue
: getPropertyCSSValue
const cssValue = await getCSSProperty.call(
this,
{
cssProperty,
pseudoElement,
}
)
return parseCSS(cssValue, cssProperty)
}
type Options = {
cssProperty: string;
pseudoElement?: PseudoElement;
}
async function getShorthandPropertyCSSValue(
this: WebdriverIO.Element,
options: Options
) {
const { pseudoElement, cssProperty } = options
const properties = getShorthandProperties(cssProperty)
if (pseudoElement) {
const cssValues = await Promise.all(
properties.map((prop) => getPseudoElementCSSValue(
this,
{
pseudoElement,
cssProperty: prop,
}
))
)
return mergeEqualSymmetricalValue(cssValues)
}
const cssValues = await Promise.all(
properties.map((prop) => this.getElementCSSValue(this.elementId, prop))
)
return mergeEqualSymmetricalValue(cssValues)
}
async function getPropertyCSSValue(
this: WebdriverIO.Element,
options: Options,
) {
const { pseudoElement, cssProperty } = options
if (pseudoElement) {
return await getPseudoElementCSSValue(
this,
{
pseudoElement,
cssProperty
}
)
}
return await this.getElementCSSValue(this.elementId, cssProperty)
}
function getShorthandProperties(cssProperty: string) {
/**
* Getting the css value of a shorthand property results in different results
* given that the behavior of `getComputedStyle` is not defined in this case.
* Therefore if we don't deal with a shorthand property run `getElementCSSValue`
* otherwise expand it and run the command for each longhand property.
*/
return cssShorthandProps.expand(cssProperty)
}
function mergeEqualSymmetricalValue(cssValues: string[]) {
/**
* merge equal symmetrical values
* - e.g. `36px 10px 36px 10px` to `36px 10px`
* - or `0px 0px 0px 0px` to `0px`
*/
let newCssValues = [...cssValues]
while ((newCssValues.length % 2) === 0) {
const mergedValues = [
newCssValues.slice(0, newCssValues.length / 2).join(' '),
newCssValues.slice(newCssValues.length / 2).join(' ')
]
const hasEqualProperties = mergedValues.every((v) => v === mergedValues[0])
if (!hasEqualProperties) {
break
}
newCssValues = newCssValues.slice(0, newCssValues.length / 2)
}
return newCssValues.join(' ')
}
async function getPseudoElementCSSValue (
elem: WebdriverIO.Element,
options: Required
): Promise {
const browser = getBrowserObject(elem)
const { cssProperty, pseudoElement } = options
const cssValue = await browser.execute(
(elem: Element, pseudoElement: string, cssProperty: string) => (window.getComputedStyle(elem, pseudoElement))[cssProperty as unknown as number],
elem as unknown as Element,
pseudoElement,
cssProperty
)
return cssValue
}