UNPKG

6.34 kBJavaScriptView Raw
1/**
2 * Copyright 2017 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16const path = require('path');
17const {JSHandle} = require('./ExecutionContext');
18const {helper, debugError} = require('./helper');
19
20class ElementHandle extends JSHandle {
21 /**
22 * @param {!Puppeteer.ExecutionContext} context
23 * @param {!Puppeteer.CDPSession} client
24 * @param {!Object} remoteObject
25 * @param {!Puppeteer.Page} page
26 */
27 constructor(context, client, remoteObject, page) {
28 super(context, client, remoteObject);
29 this._client = client;
30 this._remoteObject = remoteObject;
31 this._page = page;
32 this._disposed = false;
33 }
34
35 /**
36 * @override
37 * @return {?ElementHandle}
38 */
39 asElement() {
40 return this;
41 }
42
43 async _scrollIntoViewIfNeeded() {
44 const error = await this.executionContext().evaluate(element => {
45 if (!element.isConnected)
46 return 'Node is detached from document';
47 if (element.nodeType !== Node.ELEMENT_NODE)
48 return 'Node is not of type HTMLElement';
49 element.scrollIntoViewIfNeeded();
50 return false;
51 }, this);
52 if (error)
53 throw new Error(error);
54 }
55
56 /**
57 * @return {!Promise<{x: number, y: number}>}
58 */
59 async _visibleCenter() {
60 await this._scrollIntoViewIfNeeded();
61 const box = await this.boundingBox();
62 if (!box)
63 throw new Error('Node is not visible');
64 return {
65 x: box.x + box.width / 2,
66 y: box.y + box.height / 2
67 };
68 }
69
70 async hover() {
71 const {x, y} = await this._visibleCenter();
72 await this._page.mouse.move(x, y);
73 }
74
75 /**
76 * @param {!Object=} options
77 */
78 async click(options = {}) {
79 const {x, y} = await this._visibleCenter();
80 await this._page.mouse.click(x, y, options);
81 }
82
83 /**
84 * @param {!Array<string>} filePaths
85 * @return {!Promise}
86 */
87 async uploadFile(...filePaths) {
88 const files = filePaths.map(filePath => path.resolve(filePath));
89 const objectId = this._remoteObject.objectId;
90 return this._client.send('DOM.setFileInputFiles', { objectId, files });
91 }
92
93 async tap() {
94 const {x, y} = await this._visibleCenter();
95 await this._page.touchscreen.tap(x, y);
96 }
97
98 async focus() {
99 await this.executionContext().evaluate(element => element.focus(), this);
100 }
101
102 /**
103 * @param {string} text
104 * @param {{delay: (number|undefined)}=} options
105 */
106 async type(text, options) {
107 await this.focus();
108 await this._page.keyboard.type(text, options);
109 }
110
111 /**
112 * @param {string} key
113 * @param {!Object=} options
114 */
115 async press(key, options) {
116 await this.focus();
117 await this._page.keyboard.press(key, options);
118 }
119
120 /**
121 * @return {!Promise<?{x: number, y: number, width: number, height: number}>}
122 */
123 async boundingBox() {
124 const result = await this._client.send('DOM.getBoxModel', {
125 objectId: this._remoteObject.objectId
126 }).catch(error => void debugError(error));
127
128 if (!result)
129 return null;
130
131 const quad = result.model.border;
132 const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
133 const y = Math.min(quad[1], quad[3], quad[5], quad[7]);
134 const width = Math.max(quad[0], quad[2], quad[4], quad[6]) - x;
135 const height = Math.max(quad[1], quad[3], quad[5], quad[7]) - y;
136
137 return {x, y, width, height};
138 }
139
140 /**
141 *
142 * @param {!Object=} options
143 * @returns {!Promise<Object>}
144 */
145 async screenshot(options = {}) {
146 await this._scrollIntoViewIfNeeded();
147 const { layoutViewport: { pageX, pageY } } = await this._client.send('Page.getLayoutMetrics');
148
149 const boundingBox = await this.boundingBox();
150 if (!boundingBox)
151 throw new Error('Node is not visible');
152 const clip = Object.assign({}, boundingBox);
153 clip.x += pageX;
154 clip.y += pageY;
155 return await this._page.screenshot(Object.assign({}, {
156 clip
157 }, options));
158 }
159
160 /**
161 * @param {string} selector
162 * @return {!Promise<?ElementHandle>}
163 */
164 async $(selector) {
165 const handle = await this.executionContext().evaluateHandle(
166 (element, selector) => element.querySelector(selector),
167 this, selector
168 );
169 const element = handle.asElement();
170 if (element)
171 return element;
172 await handle.dispose();
173 return null;
174 }
175
176 /**
177 * @param {string} selector
178 * @return {!Promise<!Array<!ElementHandle>>}
179 */
180 async $$(selector) {
181 const arrayHandle = await this.executionContext().evaluateHandle(
182 (element, selector) => element.querySelectorAll(selector),
183 this, selector
184 );
185 const properties = await arrayHandle.getProperties();
186 await arrayHandle.dispose();
187 const result = [];
188 for (const property of properties.values()) {
189 const elementHandle = property.asElement();
190 if (elementHandle)
191 result.push(elementHandle);
192 }
193 return result;
194 }
195
196 /**
197 * @param {string} expression
198 * @return {!Promise<!Array<!ElementHandle>>}
199 */
200 async $x(expression) {
201 const arrayHandle = await this.executionContext().evaluateHandle(
202 (element, expression) => {
203 const document = element.ownerDocument || element;
204 const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
205 const array = [];
206 let item;
207 while ((item = iterator.iterateNext()))
208 array.push(item);
209 return array;
210 },
211 this, expression
212 );
213 const properties = await arrayHandle.getProperties();
214 await arrayHandle.dispose();
215 const result = [];
216 for (const property of properties.values()) {
217 const elementHandle = property.asElement();
218 if (elementHandle)
219 result.push(elementHandle);
220 }
221 return result;
222 }
223}
224
225module.exports = ElementHandle;
226helper.tracePublicAPI(ElementHandle);