UNPKG

ghostjs

Version:

Modern web integration test runner

263 lines (245 loc) 7.51 kB
/* eslint-disable no-new-func */ export default class Element { /** * Creates a proxy to an element on the page. * @param {object} page The current phantom/slimer page. * @param {string} selector The selector to locate the element. * @param {integer} lookupOffset The offset of the element. Used to lookup a single element in the case of a findElements() */ constructor (page, selector, lookupOffset = 0) { this.page = page this.selector = selector this.lookupOffset = lookupOffset } async click (x, y) { return this.mouse('click', x, y) } async mouse (method, xPos, yPos) { return new Promise(resolve => { this.page.evaluate((selector, lookupOffset, mouseType, xPos, yPos) => { try { var el = document.querySelectorAll(selector)[lookupOffset] var evt = document.createEvent('MouseEvents') var calculatedX = xPos || 1 var calculatedY = yPos || 1 try { var pos = el.getBoundingClientRect() if (!xPos) { calculatedX = Math.floor((pos.left + pos.right) / 2) } if (!yPos) { calculatedY = Math.floor((pos.top + pos.bottom) / 2) } } catch (e) {} evt.initMouseEvent(mouseType, true, true, window, 1, 1, 1, calculatedX, calculatedY, false, false, false, false, 0, el) el.dispatchEvent(evt) return true } catch (e) { console.log('Failed dispatching ' + mouseType + 'mouse event on ' + selector + ': ' + e) return false } }, this.selector, this.lookupOffset, method, xPos, yPos, (err, result) => { if (err) { console.error(err) } resolve(result) }) }) } /** * "Uploads" a file pointer to a form element to upload on submit. */ async file (filePath) { return new Promise(resolve => { // TODO: This won't work for element collections (when this instance has an offset) this.page.uploadFile(this.selector, filePath) resolve() }) } /** * Sets a form field to the provided value. * For non-text inputs like selects and radio options, we try to check the right value based on option name. */ async fill (setFill) { return new Promise(resolve => { this.page.evaluate((selector, lookupOffset, value) => { var el = document.querySelectorAll(selector)[lookupOffset] if (!el) { return null } // Focus on the element try { el.focus() } catch (e) { console.log('Unable to focus on element ' + el.outerHTML + ': ' + e) } var nodeName = el.nodeName.toLowerCase() switch (nodeName) { case 'input': var type = el.getAttribute('type') || 'text' switch (type.toLowerCase()) { case 'checkbox': el.checked = !!value break case 'file': throw new Error(`File support coming soon. Path: ${value}`) case 'radio': el.checked = !!value break default: el.value = value break } break case 'select': if (el.multiple) { [].forEach.call(el.options, (option) => { option.selected = value.indexOf(option.value) !== -1 }) // Search options if we can't find the value. if (el.value === '') { [].forEach.call(el.options, (option) => { option.selected = value.indexOf(option.text) !== -1 }) } } else { el.value = value // Search options if we can't find the value. if (el.value !== value) { [].some.call(el.options, (option) => { option.selected = value === option.text return value === option.text }) } } break case 'textarea': el.value = value break default: console.log('unsupported type', nodeName) } // Emulate the change and input events ['change', 'input'].forEach((name) => { var event = document.createEvent('HTMLEvents') event.initEvent(name, true, true) el.dispatchEvent(event) }) // Blur the element try { el.blur() } catch (e) { console.log('Unable to blur element ' + el.outerHTML + ': ' + e) } }, this.selector, this.lookupOffset, setFill, (err, result) => { if (err) { console.error(err) } resolve(result) }) }) } async getAttribute (attribute) { return new Promise(resolve => { this.page.evaluate((selector, lookupOffset, attribute) => { return document.querySelectorAll(selector)[lookupOffset][attribute] }, this.selector, this.lookupOffset, attribute, (err, result) => { if (err) { console.error(err) } resolve(result) }) }) } async html () { return this.getAttribute('innerHTML') } async text () { return this.getAttribute('textContent') } async isVisible () { return new Promise(resolve => { this.page.evaluate((selector, lookupOffset) => { var el = document.querySelectorAll(selector)[lookupOffset] var style try { style = window.getComputedStyle(el, null) } catch (e) { return false } if (!style) { return false } var hidden = style.visibility === 'hidden' || style.display === 'none' if (hidden) { return false } if (style.display === 'inline' || style.display === 'inline-block') { return true } return el.clientHeight > 0 && el.clientWidth > 0 }, this.selector, this.lookupOffset, (err, result) => { if (err) { console.error(err) } resolve(result) }) }) } async rect (func) { return new Promise(resolve => { this.page.evaluate((selector, lookupOffset) => { var el = document.querySelectorAll(selector)[lookupOffset] if (!el) { return null } var rect = el.getBoundingClientRect() return { top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left, height: rect.height, width: rect.width } }, this.selector, this.lookupOffset, (err, result) => { if (err) { console.error(err) } resolve(result) }) }) } async script (func, args) { if (!Array.isArray(args)) { args = [args] } return new Promise(resolve => { this.page.evaluate((func, selector, lookupOffset, args) => { var el = document.querySelectorAll(selector)[lookupOffset] args.unshift(el) var invoke = new Function( 'return ' + func )() return invoke.apply(null, args) }, func.toString(), this.selector, this.lookupOffset, args, (err, result) => { if (err) { console.error(err) } resolve(result) }) }) } }