diff --git a/src/accessibility.ts b/src/accessibility.ts index 1c8d443ec7..d6381cfa1d 100644 --- a/src/accessibility.ts +++ b/src/accessibility.ts @@ -14,48 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import * as dom from './dom'; - -export type SerializedAXNode = { - role: string, - name: string, - value?: string|number, - description?: string, - - keyshortcuts?: string, - roledescription?: string, - valuetext?: string, - - disabled?: boolean, - expanded?: boolean, - focused?: boolean, - modal?: boolean, - multiline?: boolean, - multiselectable?: boolean, - readonly?: boolean, - required?: boolean, - selected?: boolean, - - checked?: boolean|'mixed', - pressed?: boolean|'mixed', - - level?: number, - valuemin?: number, - valuemax?: number, - - autocomplete?: string, - haspopup?: string, - invalid?: string, - orientation?: string, - - children?: SerializedAXNode[] -}; +import * as types from './types'; export interface AXNode { isInteresting(insideControl: boolean): boolean; isLeafNode(): boolean; isControl(): boolean; - serialize(): SerializedAXNode; + serialize(): types.SerializedAXNode; children(): Iterable; } @@ -68,7 +35,7 @@ export class Accessibility { async snapshot(options: { interestingOnly?: boolean; root?: dom.ElementHandle; - } = {}): Promise { + } = {}): Promise { const { interestingOnly = true, root = null, @@ -98,8 +65,8 @@ function collectInterestingNodes(collection: Set, node: AXNode, insideCo collectInterestingNodes(collection, child, insideControl); } -function serializeTree(node: AXNode, whitelistedNodes?: Set): SerializedAXNode[] { - const children: SerializedAXNode[] = []; +function serializeTree(node: AXNode, whitelistedNodes?: Set): types.SerializedAXNode[] { + const children: types.SerializedAXNode[] = []; for (const child of node.children()) children.push(...serializeTree(child, whitelistedNodes)); diff --git a/src/chromium/crAccessibility.ts b/src/chromium/crAccessibility.ts index d250dcc379..fe3be4d201 100644 --- a/src/chromium/crAccessibility.ts +++ b/src/chromium/crAccessibility.ts @@ -19,6 +19,7 @@ import { CRSession } from './crConnection'; import { Protocol } from './protocol'; import * as dom from '../dom'; import * as accessibility from '../accessibility'; +import * as types from '../types'; export async function getAccessibilityTree(client: CRSession, needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> { const {nodes} = await client.send('Accessibility.getFullAXTree'); @@ -198,7 +199,7 @@ class CRAXNode implements accessibility.AXNode { return this.isLeafNode() && !!this._name; } - serialize(): accessibility.SerializedAXNode { + serialize(): types.SerializedAXNode { const properties: Map = new Map(); for (const property of this._payload.properties || []) properties.set(property.name.toLowerCase(), property.value.value); @@ -209,12 +210,12 @@ class CRAXNode implements accessibility.AXNode { if (this._payload.description) properties.set('description', this._payload.description.value); - const node: {[x in keyof accessibility.SerializedAXNode]: any} = { + const node: {[x in keyof types.SerializedAXNode]: any} = { role: this._role, name: this._payload.name ? (this._payload.name.value || '') : '' }; - const userStringProperties: Array = [ + const userStringProperties: Array = [ 'value', 'description', 'keyshortcuts', @@ -227,7 +228,7 @@ class CRAXNode implements accessibility.AXNode { node[userStringProperty] = properties.get(userStringProperty); } - const booleanProperties: Array = [ + const booleanProperties: Array = [ 'disabled', 'expanded', 'focused', @@ -249,7 +250,7 @@ class CRAXNode implements accessibility.AXNode { node[booleanProperty] = value; } - const tristateProperties: Array = [ + const tristateProperties: Array = [ 'checked', 'pressed', ]; @@ -259,7 +260,7 @@ class CRAXNode implements accessibility.AXNode { const value = properties.get(tristateProperty); node[tristateProperty] = value === 'mixed' ? 'mixed' : value === 'true' ? true : false; } - const numericalProperties: Array = [ + const numericalProperties: Array = [ 'level', 'valuemax', 'valuemin', @@ -269,7 +270,7 @@ class CRAXNode implements accessibility.AXNode { continue; node[numericalProperty] = properties.get(numericalProperty); } - const tokenProperties: Array = [ + const tokenProperties: Array = [ 'autocomplete', 'haspopup', 'invalid', @@ -281,7 +282,7 @@ class CRAXNode implements accessibility.AXNode { continue; node[tokenProperty] = value; } - return node as accessibility.SerializedAXNode; + return node as types.SerializedAXNode; } static createTree(client: CRSession, payloads: Protocol.Accessibility.AXNode[]): CRAXNode { diff --git a/src/console.ts b/src/console.ts index 71d6304d77..5bf189d873 100644 --- a/src/console.ts +++ b/src/console.ts @@ -16,12 +16,7 @@ import * as js from './javascript'; import * as util from 'util'; - -export type ConsoleMessageLocation = { - url?: string, - lineNumber?: number, - columnNumber?: number, -}; +import { ConsoleMessageLocation } from './types'; export class ConsoleMessage { private _type: string; diff --git a/src/firefox/ffAccessibility.ts b/src/firefox/ffAccessibility.ts index cd42ab6290..dd9bf3570a 100644 --- a/src/firefox/ffAccessibility.ts +++ b/src/firefox/ffAccessibility.ts @@ -19,6 +19,7 @@ import * as accessibility from '../accessibility'; import { FFSession } from './ffConnection'; import { Protocol } from './protocol'; import * as dom from '../dom'; +import * as types from '../types'; export async function getAccessibilityTree(session: FFSession, needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> { const objectId = needle ? needle._objectId : undefined; @@ -198,12 +199,12 @@ class FFAXNode implements accessibility.AXNode { return this.isLeafNode() && !!this._name.trim(); } - serialize(): accessibility.SerializedAXNode { - const node: {[x in keyof accessibility.SerializedAXNode]: any} = { + serialize(): types.SerializedAXNode { + const node: {[x in keyof types.SerializedAXNode]: any} = { role: FFRoleToARIARole.get(this._role) || this._role, name: this._name || '' }; - const userStringProperties: Array = [ + const userStringProperties: Array = [ 'name', 'value', 'description', @@ -216,7 +217,7 @@ class FFAXNode implements accessibility.AXNode { continue; node[userStringProperty] = this._payload[userStringProperty]; } - const booleanProperties: Array = [ + const booleanProperties: Array = [ 'disabled', 'expanded', 'focused', @@ -235,7 +236,7 @@ class FFAXNode implements accessibility.AXNode { continue; node[booleanProperty] = value; } - const tristateProperties: Array = [ + const tristateProperties: Array = [ 'checked', 'pressed', ]; @@ -245,7 +246,7 @@ class FFAXNode implements accessibility.AXNode { const value = this._payload[tristateProperty]; node[tristateProperty] = value; } - const numericalProperties: Array = [ + const numericalProperties: Array = [ 'level' ]; for (const numericalProperty of numericalProperties) { @@ -253,7 +254,7 @@ class FFAXNode implements accessibility.AXNode { continue; node[numericalProperty] = this._payload[numericalProperty]; } - const tokenProperties: Array = [ + const tokenProperties: Array = [ 'autocomplete', 'haspopup', 'invalid', diff --git a/src/page.ts b/src/page.ts index a0ce65a777..c80a2ed99f 100644 --- a/src/page.ts +++ b/src/page.ts @@ -26,7 +26,7 @@ import { TimeoutSettings } from './timeoutSettings'; import * as types from './types'; import { Events } from './events'; import { BrowserContext, BrowserContextBase } from './browserContext'; -import { ConsoleMessage, ConsoleMessageLocation } from './console'; +import { ConsoleMessage } from './console'; import * as accessibility from './accessibility'; import { EventEmitter } from 'events'; import { FileChooser } from './fileChooser'; @@ -281,7 +281,7 @@ export class Page extends EventEmitter { await PageBinding.dispatch(this, payload, context); } - _addConsoleMessage(type: string, args: js.JSHandle[], location: ConsoleMessageLocation, text?: string) { + _addConsoleMessage(type: string, args: js.JSHandle[], location: types.ConsoleMessageLocation, text?: string) { const message = new ConsoleMessage(type, text, args, location); const intercepted = this._frameManager.interceptConsoleMessage(message); if (intercepted || !this.listenerCount(Events.Page.Console)) diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index 820af62646..84b70a4948 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -64,6 +64,7 @@ export interface PageChannel extends Channel { on(event: 'requestFinished', callback: (params: RequestChannel) => void): this; on(event: 'requestFailed', callback: (params: RequestChannel) => void): this; on(event: 'close', callback: () => void): this; + on(event: 'console', callback: (params: ConsoleMessageChannel) => void): this; setDefaultNavigationTimeoutNoReply(params: { timeout: number }): void; setDefaultTimeoutNoReply(params: { timeout: number }): Promise; @@ -82,6 +83,20 @@ export interface PageChannel extends Channel { setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise; screenshot(params: { options?: types.ScreenshotOptions }): Promise; close(params: { options?: { runBeforeUnload?: boolean } }): Promise; + + // Input + keyboardDown(params: { key: string }): Promise; + keyboardUp(params: { key: string }): Promise; + keyboardInsertText(params: { text: string }): Promise; + keyboardType(params: { text: string, options?: { delay?: number } }): Promise; + keyboardPress(params: { key: string, options?: { delay?: number } }): Promise; + mouseMove(params: { x: number, y: number, options?: { steps?: number } }): Promise; + mouseDown(params: { options?: { button?: types.MouseButton, clickCount?: number } }): Promise; + mouseUp(params: { options?: { button?: types.MouseButton, clickCount?: number } }): Promise; + mouseClick(params: { x: number, y: number, options?: { delay?: number, button?: types.MouseButton, clickCount?: number } }): Promise; + + // A11Y + accessibilitySnapshot(params: { options: { interestingOnly?: boolean, root?: ElementHandleChannel } }): Promise; } export interface FrameChannel extends Channel { @@ -173,3 +188,5 @@ export interface ResponseChannel extends Channel { finished(): Promise; } +export interface ConsoleMessageChannel extends Channel { +} diff --git a/src/rpc/client/accessibility.ts b/src/rpc/client/accessibility.ts new file mode 100644 index 0000000000..adf6928dd1 --- /dev/null +++ b/src/rpc/client/accessibility.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications 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 { PageChannel } from '../channels'; +import { ElementHandle } from './elementHandle'; +import * as types from '../../types'; + +export class Accessibility { + private _channel: PageChannel; + + constructor(channel: PageChannel) { + this._channel = channel; + } + + snapshot(options: { interestingOnly?: boolean; root?: ElementHandle } = {}): Promise { + const root = options.root ? options.root._elementChannel : undefined; + return this._channel.accessibilitySnapshot({ options: { interestingOnly: options.interestingOnly, root } }); + } +} diff --git a/src/rpc/client/console.ts b/src/rpc/client/console.ts new file mode 100644 index 0000000000..6bb109666a --- /dev/null +++ b/src/rpc/client/console.ts @@ -0,0 +1,64 @@ +/** + * 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 util from 'util'; +import { ConsoleMessageLocation } from '../../types'; +import { JSHandle } from './jsHandle'; +import { ConsoleMessageChannel, JSHandleChannel } from '../channels'; +import { ChannelOwner } from './channelOwner'; +import { Connection } from '../connection'; + +export class ConsoleMessage extends ChannelOwner { + private _type: string = ''; + private _text: string = ''; + private _args: JSHandle[] = []; + private _location: ConsoleMessageLocation = {}; + + static from(request: ConsoleMessageChannel): ConsoleMessage { + return request._object; + } + + constructor(connection: Connection, channel: ConsoleMessageChannel) { + super(connection, channel); + } + + _initialize(params: { type: string, text: string, args: JSHandleChannel[], location: ConsoleMessageLocation }) { + this._type = params.type; + this._text = params.text; + this._args = params.args.map(JSHandle.from); + this._location = params.location; + } + + type(): string { + return this._type; + } + + text(): string { + return this._text; + } + + args(): JSHandle[] { + return this._args; + } + + location(): ConsoleMessageLocation { + return this._location; + } + + [util.inspect.custom]() { + return this.text(); + } +} diff --git a/src/rpc/client/elementHandle.ts b/src/rpc/client/elementHandle.ts index eb63d258e0..2928e6e656 100644 --- a/src/rpc/client/elementHandle.ts +++ b/src/rpc/client/elementHandle.ts @@ -21,7 +21,7 @@ import { FuncOn, JSHandle, convertArg } from './jsHandle'; import { Connection } from '../connection'; export class ElementHandle extends JSHandle { - private _elementChannel: ElementHandleChannel; + readonly _elementChannel: ElementHandleChannel; static from(handle: ElementHandleChannel): ElementHandle { return handle._object; diff --git a/src/rpc/client/input.ts b/src/rpc/client/input.ts new file mode 100644 index 0000000000..a64cb97ae6 --- /dev/null +++ b/src/rpc/client/input.ts @@ -0,0 +1,75 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * Modifications 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 types from '../../types'; +import { PageChannel } from '../channels'; + +export class Keyboard { + private _channel: PageChannel; + + constructor(channel: PageChannel) { + this._channel = channel; + } + + async down(key: string) { + await this._channel.keyboardDown({ key }); + } + + async up(key: string) { + await this._channel.keyboardUp({ key }); + } + + async insertText(text: string) { + await this._channel.keyboardInsertText({ text }); + } + + async type(text: string, options?: { delay?: number }) { + await this._channel.keyboardType({ text, options }); + } + + async press(key: string, options: { delay?: number } = {}) { + await this._channel.keyboardPress({ key, options }); + } +} + +export class Mouse { + private _channel: PageChannel; + + constructor(channel: PageChannel) { + this._channel = channel; + } + + async move(x: number, y: number, options: { steps?: number } = {}) { + await this._channel.mouseMove({ x, y, options }); + } + + async down(options: { button?: types.MouseButton, clickCount?: number } = {}) { + await this._channel.mouseDown({ options }); + } + + async up(options: { button?: types.MouseButton, clickCount?: number } = {}) { + await this._channel.mouseUp({ options }); + } + + async click(x: number, y: number, options: { delay?: number, button?: types.MouseButton, clickCount?: number } = {}) { + await this._channel.mouseClick({ x, y, options }); + } + + async dblclick(x: number, y: number, options: { delay?: number, button?: types.MouseButton } = {}) { + await this.click(x, y, { ...options, clickCount: 2 }); + } +} diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index f13d12819f..c28772f149 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -27,6 +27,9 @@ import { Frame, FunctionWithSource, GotoOptions } from './frame'; import { Func1, FuncOn, SmartHandle } from './jsHandle'; import { Request, Response, RouteHandler } from './network'; import { Connection } from '../connection'; +import { Keyboard, Mouse } from './input'; +import { Accessibility } from './accessibility'; +import { ConsoleMessage } from './console'; export class Page extends ChannelOwner { readonly pdf: ((options?: types.PDFOptions) => Promise) | undefined; @@ -39,6 +42,10 @@ export class Page extends ChannelOwner { private _viewportSize: types.Size | null = null; private _routes: { url: types.URLMatch, handler: RouteHandler }[] = []; + readonly accessibility: Accessibility; + readonly keyboard: Keyboard; + readonly mouse: Mouse; + static from(page: PageChannel): Page { return page._object; } @@ -49,6 +56,9 @@ export class Page extends ChannelOwner { constructor(connection: Connection, channel: PageChannel) { super(connection, channel); + this.accessibility = new Accessibility(channel); + this.keyboard = new Keyboard(channel); + this.mouse = new Mouse(channel); } _initialize(payload: { browserContext: BrowserContextChannel, mainFrame: FrameChannel, viewportSize: types.Size }) { @@ -64,6 +74,7 @@ export class Page extends ChannelOwner { this._channel.on('response', response => this.emit(Events.Page.Response, Response.from(response))); this._channel.on('requestFinished', request => this.emit(Events.Page.Request, Request.from(request))); this._channel.on('requestFailed', request => this.emit(Events.Page.Request, Request.from(request))); + this._channel.on('console', message => this.emit(Events.Page.Console, ConsoleMessage.from(message))); this._channel.on('close', () => this._onClose()); } @@ -230,7 +241,9 @@ export class Page extends ChannelOwner { } async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise { - return await this._channel.waitForEvent({ event }); + const result = await this._channel.waitForEvent({ event }); + if (result._object) + return result._object; } async goBack(options?: types.NavigateOptions): Promise { diff --git a/src/rpc/connection.ts b/src/rpc/connection.ts index 25811836fc..f7ccca6301 100644 --- a/src/rpc/connection.ts +++ b/src/rpc/connection.ts @@ -26,6 +26,7 @@ import { Request, Response } from './client/network'; import { Page } from './client/page'; import debug = require('debug'); import { Channel } from './channels'; +import { ConsoleMessage } from './client/console'; export class Connection { private _channels = new Map(); @@ -65,6 +66,9 @@ export class Connection { case 'elementHandle': result = new ElementHandle(this, channel); break; + case 'consoleMessage': + result = new ConsoleMessage(this, channel); + break; default: throw new Error('Missing type ' + type); } @@ -110,6 +114,8 @@ export class Connection { return obj.emit; if (prop === 'on') return obj.on; + if (prop === 'once') + return obj.once; if (prop === 'addEventListener') return obj.addListener; if (prop === 'removeEventListener') diff --git a/src/rpc/server/consoleMessageDispatcher.ts b/src/rpc/server/consoleMessageDispatcher.ts new file mode 100644 index 0000000000..85e042a879 --- /dev/null +++ b/src/rpc/server/consoleMessageDispatcher.ts @@ -0,0 +1,38 @@ +/** + * 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 { ConsoleMessage } from '../../console'; +import { ConsoleMessageChannel } from '../channels'; +import { Dispatcher, DispatcherScope } from '../dispatcher'; +import { ElementHandleDispatcher } from './elementHandlerDispatcher'; + +export class ConsoleMessageDispatcher extends Dispatcher implements ConsoleMessageChannel { + static from(scope: DispatcherScope, message: ConsoleMessage): ConsoleMessageDispatcher { + if ((message as any)[scope.dispatcherSymbol]) + return (message as any)[scope.dispatcherSymbol]; + return new ConsoleMessageDispatcher(scope, message); + } + + constructor(scope: DispatcherScope, message: ConsoleMessage) { + super(scope, message, 'consoleMessage'); + this._initialize({ + type: message.type(), + text: message.text(), + args: message.args().map(a => ElementHandleDispatcher.from(this._scope, a)), + location: message.location(), + }); + } +} diff --git a/src/rpc/server/pageDispatcher.ts b/src/rpc/server/pageDispatcher.ts index 0e35233090..f3ddc30f54 100644 --- a/src/rpc/server/pageDispatcher.ts +++ b/src/rpc/server/pageDispatcher.ts @@ -14,15 +14,17 @@ * limitations under the License. */ +import { ConsoleMessage } from '../../console'; import { Events } from '../../events'; import { Frame } from '../../frames'; import { Page } from '../../page'; import * as types from '../../types'; -import { PageChannel, ResponseChannel } from '../channels'; +import { ElementHandleChannel, PageChannel, ResponseChannel } from '../channels'; import { Dispatcher, DispatcherScope } from '../dispatcher'; import { BrowserContextDispatcher } from './browserContextDispatcher'; import { FrameDispatcher } from './frameDispatcher'; import { RequestDispatcher, ResponseDispatcher } from './networkDispatchers'; +import { ConsoleMessageDispatcher } from './consoleMessageDispatcher'; export class PageDispatcher extends Dispatcher implements PageChannel { private _page: Page; @@ -57,6 +59,7 @@ export class PageDispatcher extends Dispatcher implements PageChannel { page.on(Events.Page.Response, response => this._dispatchEvent('response', ResponseDispatcher.from(this._scope, response))); page.on(Events.Page.RequestFinished, request => this._dispatchEvent('requestFinished', ResponseDispatcher.from(this._scope, request))); page.on(Events.Page.RequestFailed, request => this._dispatchEvent('requestFailed', ResponseDispatcher.from(this._scope, request))); + page.on(Events.Page.Console, message => this._dispatchEvent('console', ConsoleMessageDispatcher.from(this._scope, message))); } async setDefaultNavigationTimeoutNoReply(params: { timeout: number }) { @@ -83,6 +86,9 @@ export class PageDispatcher extends Dispatcher implements PageChannel { } async waitForEvent(params: { event: string }): Promise { + const result = await this._page.waitForEvent(params.event); + if (result instanceof ConsoleMessage) + return ConsoleMessageDispatcher.from(this._scope, result); } async goBack(params: { options?: types.NavigateOptions }): Promise { @@ -123,6 +129,49 @@ export class PageDispatcher extends Dispatcher implements PageChannel { return await this._page.title(); } + async keyboardDown(params: { key: string }): Promise { + await this._page.keyboard.down(params.key); + } + + async keyboardUp(params: { key: string }): Promise { + await this._page.keyboard.up(params.key); + } + + async keyboardInsertText(params: { text: string }): Promise { + await this._page.keyboard.insertText(params.text); + } + + async keyboardType(params: { text: string, options?: { delay?: number } }): Promise { + await this._page.keyboard.type(params.text, params.options); + } + + async keyboardPress(params: { key: string, options?: { delay?: number } }): Promise { + await this._page.keyboard.press(params.key, params.options); + } + + async mouseMove(params: { x: number, y: number, options?: { steps?: number } }): Promise { + await this._page.mouse.move(params.x, params.y, params.options); + } + + async mouseDown(params: { options?: { button?: types.MouseButton, clickCount?: number } }): Promise { + await this._page.mouse.down(params.options); + } + + async mouseUp(params: { options?: { button?: types.MouseButton, clickCount?: number } }): Promise { + await this._page.mouse.up(params.options); + } + + async mouseClick(params: { x: number, y: number, options?: { delay?: number, button?: types.MouseButton, clickCount?: number } }): Promise { + await this._page.mouse.click(params.x, params.y, params.options); + } + + async accessibilitySnapshot(params: { options: { interestingOnly?: boolean, root?: ElementHandleChannel } }): Promise { + return await this._page.accessibility.snapshot({ + interestingOnly: params.options.interestingOnly, + root: params.options.root ? params.options.root._object : undefined + }); + } + _onFrameAttached(frame: Frame) { this._dispatchEvent('frameAttached', FrameDispatcher.from(this._scope, frame)); } diff --git a/src/types.ts b/src/types.ts index bed3544f91..30ade8652b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ /** - * Copyright (c) Microsoft Corporation. + * Copyright 2018 Google Inc. All rights reserved. + * Modifications 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. @@ -263,3 +264,44 @@ export type ConnectOptions = { slowMo?: number, timeout?: number, }; + +export type SerializedAXNode = { + role: string, + name: string, + value?: string|number, + description?: string, + + keyshortcuts?: string, + roledescription?: string, + valuetext?: string, + + disabled?: boolean, + expanded?: boolean, + focused?: boolean, + modal?: boolean, + multiline?: boolean, + multiselectable?: boolean, + readonly?: boolean, + required?: boolean, + selected?: boolean, + + checked?: boolean | 'mixed', + pressed?: boolean | 'mixed', + + level?: number, + valuemin?: number, + valuemax?: number, + + autocomplete?: string, + haspopup?: string, + invalid?: string, + orientation?: string, + + children?: SerializedAXNode[] +}; + +export type ConsoleMessageLocation = { + url?: string, + lineNumber?: number, + columnNumber?: number, +}; diff --git a/src/webkit/wkAccessibility.ts b/src/webkit/wkAccessibility.ts index 5bcc413add..2beeaf6170 100644 --- a/src/webkit/wkAccessibility.ts +++ b/src/webkit/wkAccessibility.ts @@ -17,6 +17,7 @@ import * as accessibility from '../accessibility'; import { WKSession } from './wkConnection'; import { Protocol } from './protocol'; import * as dom from '../dom'; +import * as types from '../types'; export async function getAccessibilityTree(session: WKSession, needle?: dom.ElementHandle) { const objectId = needle ? needle._objectId : undefined; @@ -166,8 +167,8 @@ class WKAXNode implements accessibility.AXNode { return false; } - serialize(): accessibility.SerializedAXNode { - const node: accessibility.SerializedAXNode = { + serialize(): types.SerializedAXNode { + const node: types.SerializedAXNode = { role: WKRoleToARIARole.get(this._payload.role) || this._payload.role, name: this._name(), }; @@ -184,7 +185,7 @@ class WKAXNode implements accessibility.AXNode { if ('value' in this._payload && this._payload.role !== 'text') node.value = this._payload.value; - const userStringProperties: Array = [ + const userStringProperties: Array = [ 'keyshortcuts', 'valuetext' ]; @@ -194,7 +195,7 @@ class WKAXNode implements accessibility.AXNode { (node as any)[userStringProperty] = this._payload[userStringProperty]; } - const booleanProperties: Array = [ + const booleanProperties: Array = [ 'disabled', 'expanded', 'focused', @@ -226,7 +227,7 @@ class WKAXNode implements accessibility.AXNode { const value = this._payload[tristateProperty]; node[tristateProperty] = value === 'mixed' ? 'mixed' : value === 'true' ? true : false; } - const numericalProperties: Array = [ + const numericalProperties: Array = [ 'level', 'valuemax', 'valuemin', @@ -236,7 +237,7 @@ class WKAXNode implements accessibility.AXNode { continue; (node as any)[numericalProperty] = (this._payload as any)[numericalProperty]; } - const tokenProperties: Array = [ + const tokenProperties: Array = [ 'autocomplete', 'haspopup', 'invalid', diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index a1f62da539..e4d7438480 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -36,7 +36,6 @@ import { WKBrowserContext } from './wkBrowser'; import { selectors } from '../selectors'; import * as jpeg from 'jpeg-js'; import * as png from 'pngjs'; -import { ConsoleMessageLocation } from '../console'; import { JSHandle } from '../javascript'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; @@ -63,7 +62,7 @@ export class WKPage implements PageDelegate { private _firstNonInitialNavigationCommittedPromise: Promise; private _firstNonInitialNavigationCommittedFulfill = () => {}; _firstNonInitialNavigationCommittedReject = (e: Error) => {}; - private _lastConsoleMessage: { derivedType: string, text: string, handles: JSHandle[]; count: number, location: ConsoleMessageLocation; } | null = null; + private _lastConsoleMessage: { derivedType: string, text: string, handles: JSHandle[]; count: number, location: types.ConsoleMessageLocation; } | null = null; // Holds window features for the next popup being opened via window.open, // until the popup page proxy arrives.