/** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as structs from '../../types/structs'; import * as api from '../../types/types'; import * as channels from '../protocol/channels'; import * as util from 'util'; import { isRegExp, monotonicTime } from '../utils/utils'; import { ElementHandle } from './elementHandle'; import { Frame } from './frame'; import { FilePayload, FrameExpectOptions, Rect, SelectOption, SelectOptionOptions, TimeoutOptions } from './types'; import { parseResult, serializeArgument } from './jsHandle'; import { escapeWithQuotes } from '../utils/stringUtils'; export class Locator implements api.Locator { private _frame: Frame; private _selector: string; constructor(frame: Frame, selector: string) { this._frame = frame; this._selector = selector; } private async _withElement(task: (handle: ElementHandle, timeout?: number) => Promise, timeout?: number): Promise { timeout = this._frame.page()._timeoutSettings.timeout({ timeout }); const deadline = timeout ? monotonicTime() + timeout : 0; return this._frame._wrapApiCall(async () => { const result = await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, state: 'attached', timeout }); const handle = ElementHandle.fromNullable(result.element) as ElementHandle | null; if (!handle) throw new Error(`Could not resolve ${this._selector} to DOM Element`); try { return await task(handle, deadline ? deadline - monotonicTime() : 0); } finally { await handle.dispose(); } }); } async boundingBox(options?: TimeoutOptions): Promise { return this._withElement(h => h.boundingBox(), options?.timeout); } async check(options: channels.ElementHandleCheckOptions = {}) { return this._frame.check(this._selector, { strict: true, ...options }); } async click(options: channels.ElementHandleClickOptions = {}): Promise { return this._frame.click(this._selector, { strict: true, ...options }); } async dblclick(options: channels.ElementHandleDblclickOptions = {}): Promise { return this._frame.dblclick(this._selector, { strict: true, ...options }); } async dispatchEvent(type: string, eventInit: Object = {}, options?: TimeoutOptions) { return this._frame.dispatchEvent(this._selector, type, eventInit, { strict: true, ...options }); } async dragTo(target: Locator, options: channels.FrameDragAndDropOptions = {}) { return this._frame.dragAndDrop(this._selector, target._selector, { ...options, strict: true, }); } async evaluate(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions): Promise { return this._withElement(h => h.evaluate(pageFunction, arg), options?.timeout); } async evaluateAll(pageFunction: structs.PageFunctionOn, arg?: Arg): Promise { return this._frame.$$eval(this._selector, pageFunction, arg); } async evaluateHandle(pageFunction: structs.PageFunctionOn, arg?: Arg, options?: TimeoutOptions): Promise> { return this._withElement(h => h.evaluateHandle(pageFunction, arg), options?.timeout); } async fill(value: string, options: channels.ElementHandleFillOptions = {}): Promise { return this._frame.fill(this._selector, value, { strict: true, ...options }); } locator(selector: string): Locator { return new Locator(this._frame, this._selector + ' >> ' + selector); } withText(text: string | RegExp): Locator { const matcher = isRegExp(text) ? 'text-matches' : 'has-text'; const source = escapeWithQuotes(isRegExp(text) ? text.source : text, '"'); return new Locator(this._frame, this._selector + ` >> :scope:${matcher}(${source})`); } frameLocator(selector: string): FrameLocator { return new FrameLocator(this._frame, this._selector + ' >> ' + selector); } async elementHandle(options?: TimeoutOptions): Promise> { return await this._frame.waitForSelector(this._selector, { strict: true, state: 'attached', ...options })!; } async elementHandles(): Promise[]> { return this._frame.$$(this._selector); } first(): Locator { return new Locator(this._frame, this._selector + ' >> nth=0'); } last(): Locator { return new Locator(this._frame, this._selector + ` >> nth=-1`); } nth(index: number): Locator { return new Locator(this._frame, this._selector + ` >> nth=${index}`); } async focus(options?: TimeoutOptions): Promise { return this._frame.focus(this._selector, { strict: true, ...options }); } async count(): Promise { return this.evaluateAll(ee => ee.length); } async getAttribute(name: string, options?: TimeoutOptions): Promise { return this._frame.getAttribute(this._selector, name, { strict: true, ...options }); } async hover(options: channels.ElementHandleHoverOptions = {}): Promise { return this._frame.hover(this._selector, { strict: true, ...options }); } async innerHTML(options?: TimeoutOptions): Promise { return this._frame.innerHTML(this._selector, { strict: true, ...options }); } async innerText(options?: TimeoutOptions): Promise { return this._frame.innerText(this._selector, { strict: true, ...options }); } async inputValue(options?: TimeoutOptions): Promise { return this._frame.inputValue(this._selector, { strict: true, ...options }); } async isChecked(options?: TimeoutOptions): Promise { return this._frame.isChecked(this._selector, { strict: true, ...options }); } async isDisabled(options?: TimeoutOptions): Promise { return this._frame.isDisabled(this._selector, { strict: true, ...options }); } async isEditable(options?: TimeoutOptions): Promise { return this._frame.isEditable(this._selector, { strict: true, ...options }); } async isEnabled(options?: TimeoutOptions): Promise { return this._frame.isEnabled(this._selector, { strict: true, ...options }); } async isHidden(options?: TimeoutOptions): Promise { return this._frame.isHidden(this._selector, { strict: true, ...options }); } async isVisible(options?: TimeoutOptions): Promise { return this._frame.isVisible(this._selector, { strict: true, ...options }); } async press(key: string, options: channels.ElementHandlePressOptions = {}): Promise { return this._frame.press(this._selector, key, { strict: true, ...options }); } async screenshot(options: channels.ElementHandleScreenshotOptions & { path?: string } = {}): Promise { return this._withElement((h, timeout) => h.screenshot({ ...options, timeout }), options.timeout); } async scrollIntoViewIfNeeded(options: channels.ElementHandleScrollIntoViewIfNeededOptions = {}) { return this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({ ...options, timeout }), options.timeout); } async selectOption(values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise { return this._frame.selectOption(this._selector, values, { strict: true, ...options }); } async selectText(options: channels.ElementHandleSelectTextOptions = {}): Promise { return this._withElement((h, timeout) => h.selectText({ ...options, timeout }), options.timeout); } async setChecked(checked: boolean, options?: channels.ElementHandleCheckOptions) { if (checked) await this.check(options); else await this.uncheck(options); } async setInputFiles(files: string | FilePayload | string[] | FilePayload[], options: channels.ElementHandleSetInputFilesOptions = {}) { return this._frame.setInputFiles(this._selector, files, { strict: true, ...options }); } async tap(options: channels.ElementHandleTapOptions = {}): Promise { return this._frame.tap(this._selector, { strict: true, ...options }); } async textContent(options?: TimeoutOptions): Promise { return this._frame.textContent(this._selector, { strict: true, ...options }); } async type(text: string, options: channels.ElementHandleTypeOptions = {}): Promise { return this._frame.type(this._selector, text, { strict: true, ...options }); } async uncheck(options: channels.ElementHandleUncheckOptions = {}) { return this._frame.uncheck(this._selector, { strict: true, ...options }); } async allInnerTexts(): Promise { return this._frame.$$eval(this._selector, ee => ee.map(e => (e as HTMLElement).innerText)); } async allTextContents(): Promise { return this._frame.$$eval(this._selector, ee => ee.map(e => e.textContent || '')); } waitFor(options: channels.FrameWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise; waitFor(options?: channels.FrameWaitForSelectorOptions): Promise; async waitFor(options?: channels.FrameWaitForSelectorOptions): Promise { await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, omitReturnValue: true, ...options }); } async _expect(expression: string, options: Omit & { expectedValue?: any }): Promise<{ matches: boolean, received?: any, log?: string[] }> { const params: channels.FrameExpectParams = { selector: this._selector, expression, ...options, isNot: !!options.isNot }; if (options.expectedValue) params.expectedValue = serializeArgument(options.expectedValue); const result = (await this._frame._channel.expect(params)); if (result.received !== undefined) result.received = parseResult(result.received); return result; } [util.inspect.custom]() { return this.toString(); } toString() { return `Locator@${this._selector}`; } } export class FrameLocator implements api.FrameLocator { private _frame: Frame; private _frameSelector: string; constructor(frame: Frame, selector: string) { this._frame = frame; this._frameSelector = selector; } locator(selector: string): Locator { return new Locator(this._frame, this._frameSelector + ' >> control=enter-frame >> ' + selector); } frameLocator(selector: string): FrameLocator { return new FrameLocator(this._frame, this._frameSelector + ' >> control=enter-frame >> ' + selector); } first(): FrameLocator { return new FrameLocator(this._frame, this._frameSelector + ' >> nth=0'); } last(): FrameLocator { return new FrameLocator(this._frame, this._frameSelector + ` >> nth=-1`); } nth(index: number): FrameLocator { return new FrameLocator(this._frame, this._frameSelector + ` >> nth=${index}`); } }