Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452import type { ParsedSelector } from 'ivya'import type { LocatorByRoleOptions, LocatorFilterOptions, LocatorOptions, LocatorScreenshotOptions, MarkOptions, SelectorOptions, UserEventClearOptions, UserEventClickOptions, UserEventDragAndDropOptions, UserEventFillOptions, UserEventHoverOptions, UserEventSelectOptions, UserEventUploadOptions, UserEventWheelOptions,} from 'vitest/browser'import { asLocator, getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector, Ivya,} from 'ivya'import { page, server, utils } from 'vitest/browser'import { __INTERNAL, getSafeTimers } from 'vitest/internal/browser'import { ensureAwaited, getBrowserState, getWorkerState } from '../utils'import { LocatorAction, resolveActionTimeout, UploadAction } from './action'import { convertElementToCssSelector, escapeForTextSelector, isLocator, resolveUserEventWheelOptions,} from './tester-utils'import { recordBrowserTraceEntry } from './trace'
export { ensureAwaited } from '../utils'export { processTimeoutOptions } from './action'export { convertElementToCssSelector, getIframeScale } from './tester-utils'export { getByAltTextSelector, getByLabelSelector, getByPlaceholderSelector, getByRoleSelector, getByTestIdSelector, getByTextSelector, getByTitleSelector,} from 'ivya'
__INTERNAL._asLocator = asLocator
const now = Date.nowconst waitForIntervals = [0, 20, 50, 100, 100, 500]
function sleep(ms: number): Promise<void> { const { setTimeout } = getSafeTimers() return new Promise((resolve) => setTimeout(resolve, ms))}
export const selectorEngine: Ivya = Ivya.create({ exact: server.config.browser.locators.exact, browser: ((name: string) => { switch (name) { case 'edge': case 'chrome': return 'chromium' case 'safari': return 'webkit' default: return name as 'webkit' | 'firefox' | 'chromium' } })(server.config.browser.name), testIdAttribute: server.config.browser.locators.testIdAttribute,})
getBrowserState().selectorEngine = selectorEngine
const kLocator = Symbol.for('$$vitest:locator')
export abstract class Locator { public abstract selector: string
private _parsedSelector: ParsedSelector | undefined protected _container?: Element | undefined protected _pwSelector?: string | undefined protected _pwLocator?: string | undefined protected _errorSource?: Error
constructor() { Object.defineProperty(this, kLocator, { enumerable: false, writable: false, configurable: false, value: kLocator, }) }
public click(options?: UserEventClickOptions): Promise<void> { return this.action('__vitest_click', [], options) }
public dblClick(options?: UserEventClickOptions): Promise<void> { return this.action('__vitest_dblClick', [], options) }
public tripleClick(options?: UserEventClickOptions): Promise<void> { return this.action('__vitest_tripleClick', [], options) }
public wheel(options: UserEventWheelOptions): Promise<void> { return ensureAwaited<void>(async (error) => { await getBrowserState().commands.triggerCommand<void>( '__vitest_wheel', [this.serialize(), resolveUserEventWheelOptions(options)], error, )
const browser = getBrowserState().config.browser.name
// looks like on Chromium the scroll event gets dispatched a frame later if (browser === 'chromium' || browser === 'chrome') { return new Promise((resolve) => { requestAnimationFrame(() => { resolve() }) }) } }) }
public clear(options?: UserEventClearOptions): Promise<void> { return this.action('__vitest_clear', [], options) }
public hover(options?: UserEventHoverOptions): Promise<void> { return this.action('__vitest_hover', [], options) }
public unhover(options?: UserEventHoverOptions): Promise<void> { return this.action('__vitest_hover', [], options, { selector: 'html > body', locator: "locator('body')", }) }
public fill(text: string, options?: UserEventFillOptions): Promise<void> { return this.action('__vitest_fill', [text], options) }
public upload( files: string | string[] | File | File[], options?: UserEventUploadOptions, ): Promise<void> { return new UploadAction(this.serialize(), files, options, this._errorSource) }
public dropTo(target: Locator, options: UserEventDragAndDropOptions = {}): Promise<void> { return this.action('__vitest_dragAndDrop', [target.toJSON()], options, this.toJSON()) }
public selectOptions( value: HTMLElement | HTMLElement[] | Locator | Locator[] | string | string[], options?: UserEventSelectOptions, ): Promise<void> { const values = (Array.isArray(value) ? value : [value]).map((v) => { if (typeof v !== 'string') { const element: SerializedLocator = isLocator(v) ? v.serialize() : { selector: convertElementToCssSelector(v), locator: __INTERNAL._asLocator( 'javascript', selectorEngine.generateSelectorSimple(v), ), } return { element } } return v }) return this.action('__vitest_selectOptions', [values], options) }
public screenshot(options: Omit<LocatorScreenshotOptions, 'base64'> & { base64: true }): Promise<{ path: string base64: string }> public screenshot(options?: LocatorScreenshotOptions): Promise<string> public screenshot(options?: LocatorScreenshotOptions): Promise< | string | { path: string base64: string } > { return page.screenshot({ ...options, element: this, }) }
public mark(name: string, options?: MarkOptions): Promise<void> { const currentTest = getWorkerState().current const hasActiveTrace = !!currentTest && getBrowserState().activeTraceTaskIds.has(currentTest.id) const hasActiveTraceView = !!currentTest && getBrowserState().browserTraceAttempts.has(currentTest.id) if (!currentTest || (!hasActiveTrace && !hasActiveTraceView)) { return Promise.resolve() } return ensureAwaited(async (error) => { if (hasActiveTraceView) { await recordBrowserTraceEntry(currentTest, { name, kind: options?.kind ?? 'mark', element: this.serialize(), stack: options?.stack ?? error?.stack, }) } if (!hasActiveTrace) { return Promise.resolve() } return getBrowserState().commands.triggerCommand<void>( '__vitest_markTrace', [ { name, element: this.serialize(), stack: options?.stack ?? error?.stack, }, ], error, ) }) }
protected abstract locator(selector: string): Locator protected abstract elementLocator(element: Element): Locator
public getByRole(role: string, options?: LocatorByRoleOptions): Locator { return this.locator(getByRoleSelector(role, options)) }
public getByAltText(text: string | RegExp, options?: LocatorOptions): Locator { return this.locator(getByAltTextSelector(text, options)) }
public getByLabelText(text: string | RegExp, options?: LocatorOptions): Locator { return this.locator(getByLabelSelector(text, options)) }
public getByPlaceholder(text: string | RegExp, options?: LocatorOptions): Locator { return this.locator(getByPlaceholderSelector(text, options)) }
public getByTestId(testId: string | RegExp): Locator { return this.locator(getByTestIdSelector(server.config.browser.locators.testIdAttribute, testId)) }
public getByText(text: string | RegExp, options?: LocatorOptions): Locator { return this.locator(getByTextSelector(text, options)) }
public getByTitle(title: string | RegExp, options?: LocatorOptions): Locator { return this.locator(getByTitleSelector(title, options)) }
public filter(filter: LocatorFilterOptions): Locator { const selectors = []
if (filter?.hasText) { selectors.push(`internal:has-text=${escapeForTextSelector(filter.hasText, false)}`) }
if (filter?.hasNotText) { selectors.push(`internal:has-not-text=${escapeForTextSelector(filter.hasNotText, false)}`) }
if (filter?.has) { const locator = filter.has as Locator selectors.push(`internal:has=${JSON.stringify(locator._pwSelector || locator.selector)}`) }
if (filter?.hasNot) { const locator = filter.hasNot as Locator selectors.push(`internal:has-not=${JSON.stringify(locator._pwSelector || locator.selector)}`) }
if (!selectors.length) { throw new Error(`Locator.filter expects at least one filter. None provided.`) }
return this.locator(selectors.join(' >> ')) }
public and(locator: Locator): Locator { return this.locator(`internal:and=${JSON.stringify(locator._pwSelector || locator.selector)}`) }
public or(locator: Locator): Locator { return this.locator(`internal:or=${JSON.stringify(locator._pwSelector || locator.selector)}`) }
public query(): HTMLElement | SVGElement | null { const parsedSelector = this._parsedSelector || (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector)) return selectorEngine.querySelector(parsedSelector, document.documentElement, true) as | HTMLElement | SVGElement }
public element(): HTMLElement | SVGElement { const element = this.query() if (!element) { throw utils.getElementError(this, this._container || document.body) } return element }
public elements(): (HTMLElement | SVGElement)[] { const parsedSelector = this._parsedSelector || (this._parsedSelector = selectorEngine.parseSelector(this._pwSelector || this.selector)) return selectorEngine.querySelectorAll(parsedSelector, document.documentElement) as ( | HTMLElement | SVGElement )[] }
public get length(): number { return this.elements().length }
public all(): Locator[] { return this.elements().map((element) => this.elementLocator(element)) }
public nth(index: number): Locator { return this.locator(`nth=${index}`) }
public first(): Locator { return this.nth(0) }
public last(): Locator { return this.nth(-1) }
public toString(): string { return this.selector }
public serialize(): SerializedLocator { return { selector: this.selector, locator: this.asLocator(), _pwSelector: this._pwSelector, } }
public asLocator(): string { return ( this._pwLocator || (this._pwLocator = asLocator('javascript', this._pwSelector || this.selector)) ) }
public toJSON(): SerializedLocator { return this.serialize() }
public async findElement(options_: SelectorOptions = {}): Promise<HTMLElement | SVGElement> { const timeout = resolveActionTimeout(options_) const strict = options_?.strict ?? true const startTime = now() let intervalIndex = 0 while (true) { const elements = this.elements() if (elements.length === 1) { return elements[0] } if (elements.length > 1) { if (strict) { throw createStrictModeViolationError(this, elements) } return elements[0] } const elapsed = now() - startTime const isLastCall = timeout != null && elapsed >= timeout if (isLastCall) { throw utils.getElementError(this, this._container || document.body) } const interval = waitForIntervals[Math.min(intervalIndex++, waitForIntervals.length - 1)] const nextInterval = timeout != null ? Math.min(interval, timeout - elapsed) : interval await sleep(nextInterval) } }
private action( command: string, args: unknown[], options?: { timeout?: number }, target: SerializedLocator = this.serialize(), ): Promise<void> { return new LocatorAction(target, command, args, options, this._errorSource) }}
/** * @deprecated */export function triggerCommandWithTrace<T>(options: { name: string arguments: unknown[] errorSource?: Error | undefined}): Promise<T> { return getBrowserState().commands.triggerCommand<T>( options.name, options.arguments, options.errorSource, )}
export interface SerializedLocator { selector: string locator: string /** * @internal */ _pwSelector?: string}
function createStrictModeViolationError(locator: Locator, matches: Element[]) { const infos = matches.slice(0, 10).map((m) => ({ preview: selectorEngine.previewNode(m), selector: selectorEngine.generateSelectorSimple(m), })) const lines = infos.map( (info, i) => `\n ${i + 1}) ${info.preview} aka ${asLocator('javascript', info.selector)}`, ) if (infos.length < matches.length) { lines.push('\n ...') } return new Error( `strict mode violation: ${locator.asLocator()} resolved to ${matches.length} elements:${lines.join('')}\n`, )}