I have code similar to this gist. Basically I am trying to use ESM with mocha so I have the hacky file with...
let driver = await import("./WireDriver.mjs");
The problem I am having is driver does not come back as either a promise or the actual driver. My breakpoints are not working thanks to an ancient version of chrome we use, but when I run...
console.log(`asdsa ${typeof d} ${Object.keys(d)} ${Object.getOwnPropertyNames(d)}`);
Comes back as
asdsa object default default
How do I get the await to actually return the driver object?
UPDATE
I tried simplifying the WireDriver to...
const test = () => {
return new Promise((res, rej) =>{
res("This is a test");
});
};
export {test}
Then tried to use it like this...
const base = await import("./WireDriver.mjs");
base.test().then((test)=>{
console.log(`Is driver loaded? ${test}`);
});
And now when I run I get...
UnhandledPromiseRejectionWarning: TypeError: base.test.then is not a function
This version seems to have worked...
import {BasePage} from "./pages/BasePage";
const driver = ()=>{
let page = new BasePage();
return page.driver;
};
export {driver}
(async () => {
const base = await import("./WireDriver.mjs");
const driver = await base.driver();
})
Related
I've initialised a basic WDIO project using mocha framework.
I have a class below
const axios = require('axios');
class Joke{
async getJoke(){
const response = axios.get('https://api.chucknorris.io/jokes/random');
return response;
}
}
module.exports = new Joke();
This is my spec file:
const Joke = require("./Joke");
describe("GET random jokes", () => {
it("responds with json", async () => {
const res = browser.call(async() => await Joke.getJoke());
console.log(res);
});
});
The console shows that my test has passed however there's no output of the response. Been trying to crack my head at this, could anyone point out what the issue is?
Aren't you missing an await here before browser.call?
const res = **await** browser.call(async() => await Joke.getJoke());
If you are using Webdriver in sync mode, there is no need to await browser.call as suggested in the previous comment, since browser.call is used to wrap async code to perform like sync code. However, the await is missing when calling axios.get.
const response = await axios.get('https://api.chucknorris.io/jokes/random');
I have a use case where I need to call an external API, parse the JSON that is returned and populate a form field in a web page all within a Selenium script written using Node JS.
Something like this:
// in Selenium script get the form field
let inputElement = await getElementById(driver, "my-id");
// then call an API including callback function
// in the callback function with the JSON response from the API
const myText = response.data.text;
await inputElement.sendKeys(myText,Key.ENTER);
I actually not even sure where to start with this - because I would be adding asynchronous code (the API call and waiting for the response in the callback) to the existing asynchronous code that is running as part of the Selenium script. And I need to not lose references to the web driver and the input element.
Some advice and recommendations to get me going would be very helpful.
If you are using node's inbuild https module the you can do something like this..
const { Builder, By, Key, until } = require("selenium-webdriver");
const https = require("https");
(async function example() {
let driver = await new Builder().forBrowser("chrome").build();
try {
await driver.get("http://www.google.com/ncr");
await https.get("https://jsonplaceholder.typicode.com/users/1", (resp) => {
let data = "";
resp.on("data", (chunk) => {
data += chunk;
});
resp.on("end", async () => {
// console.log(JSON.parse(data)["name"]);
await driver
.findElement(By.name("q"))
.sendKeys(JSON.parse(data)["name"], Key.RETURN);
});
});
await driver.wait(until.titleContains("- Google Search"), 1000);
} finally {
await driver.quit();
}
})();
Or if you are already using library like axios, then you can do something like this
const { Builder, By, Key, until } = require("selenium-webdriver");
const axios = require("axios");
(async function example() {
let driver = await new Builder().forBrowser("chrome").build();
try {
await driver.get("http://www.google.com/ncr");
const { data } = await axios.get(
"https://jsonplaceholder.typicode.com/users/1"
);
await driver.findElement(By.name("q")).sendKeys(data["name"], Key.RETURN);
await driver.wait(until.titleContains("- Google Search"), 1000);
} finally {
await driver.quit();
}
})();
Hope this is what you are looking for..
Is there a way to use a callback for puppeteer.launch instead of wrapping it in an async IIFE?
(async () => {
const browser = await puppeteer.launch();
...
browser.close()
})();
could be simplified to
puppeteer.launch(function(browser) {
...
//browser automatically closes ideally
});
This has probably been answered somewhere but I couldn't find anything with a quick google search.
If you want it to work in that way, you could probably monkey-patch puppeteer:
const realLaunch = puppeteer.launch;
puppeteer.launch = async (callback) => {
const browser = await realLaunch();
callback(browser);
browser.close();
}
But that's going to be confusing to contributors to your project that are used to the Puppeteer api as published in their documentation, so personally I wouldn't...
I'm new to Mocha and Selenium for testing an Express application. I have the following simple code, but I can't figure out what's going wrong.
describe("authenticateWithGoogle", function() {
it("return a valid access token for our tests", function() {
return new Promise( function(resolve) {
var driver = new Builder().forBrowser('chrome').build();
driver.get('https://www.google.com');
driver.wait("Google",5000).then( (quitDriver,handleFailure) => {
console.log("wait over");
assert.ok(true);
});
resolve();
}).then();
});
});
I receive the follow error when I run 'mocha':
TypeError: Wait condition must be a promise-like object, function, or a Condition object
This occurs on the 'driver.wait' line in the code above. I really don't understand what the errors means.
I tried same selenium-webdriver 4.0.0 alpha.1 and it worked.
Based on its example, it uses async await so I use same way.
const {Builder, By, Key, until} = require('selenium-webdriver');
const chai = require('chai');
const assert = chai.assert;
describe("authenticateWithGoogle", function() {
it("return a valid access token for our tests", async function() {
this.timeout(5000);
let driver = await new Builder()
.usingServer('http://localhost:4444/wd/hub')
.forBrowser('chrome').build();
try {
await driver.get('http://www.google.com');
await driver.wait(until.titleIs('Google'), 1000);
} finally {
assert.ok(true);
await driver.quit();
}
});
});
My Setup:
node 8.9
selenium-standalone https://www.npmjs.com/package/selenium-standalone
Yes, for testing oauth you can create integration/e2e test for it. You're already in the right path.
Hope it helps
I'm trying to use headless Chrome and Puppeteer to run our Javascript tests, but I can't extract the results from the page. Based on this answer, it looks like I should use page.evaluate(). That section even has an example that looks like what I need.
const bodyHandle = await page.$('body');
const html = await page.evaluate(body => body.innerHTML, bodyHandle);
await bodyHandle.dispose();
As a full example, I tried to convert that to a script that will extract my name from my user profile on Stack Overflow. Our project is using Node 6, so I converted the await expressions to use .then().
const puppeteer = require('puppeteer');
puppeteer.launch().then(function(browser) {
browser.newPage().then(function(page) {
page.goto('https://stackoverflow.com/users/4794').then(function() {
page.$('h2.user-card-name').then(function(heading_handle) {
page.evaluate(function(heading) {
return heading.innerText;
}, heading_handle).then(function(result) {
console.info(result);
browser.close();
}, function(error) {
console.error(error);
browser.close();
});
});
});
});
});
When I run that, I get this error:
$ node get_user.js
TypeError: Converting circular structure to JSON
at Object.stringify (native)
at args.map.x (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/helper.js:30:43)
at Array.map (native)
at Function.evaluationString (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/helper.js:30:29)
at Frame.<anonymous> (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:376:31)
at next (native)
at step (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:355:24)
at Promise (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:373:12)
at fn (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:351:10)
at Frame._rawEvaluate (/mnt/data/don/git/Kive/node_modules/puppeteer/node6/FrameManager.js:375:3)
The problem seems to be with serializing the input parameter to page.evaluate(). I can pass in strings and numbers, but not element handles. Is the example wrong, or is it a problem with Node 6? How can I extract the text of a DOM node?
I found three solutions to this problem, depending on how complicated your extraction is. The simplest option is a related function that I hadn't noticed: page.$eval(). It basically does what I was trying to do: combines page.$() and page.evaluate(). Here's an example that works:
const puppeteer = require('puppeteer');
puppeteer.launch().then(function(browser) {
browser.newPage().then(function(page) {
page.goto('https://stackoverflow.com/users/4794').then(function() {
page.$eval('h2.user-card-name', function(heading) {
return heading.innerText;
}).then(function(result) {
console.info(result);
browser.close();
});
});
});
});
That gives me the expected result:
$ node get_user.js
Don Kirkby top 2% overall
I wanted to extract something more complicated, but I finally realized that the evaluation function is running in the context of the page. That means you can use any tools that are loaded in the page, and then just send strings and numbers back and forth. In this example, I use jQuery in a string to extract what I want:
const puppeteer = require('puppeteer');
puppeteer.launch().then(function(browser) {
browser.newPage().then(function(page) {
page.goto('https://stackoverflow.com/users/4794').then(function() {
page.evaluate("$('h2.user-card-name').text()").then(function(result) {
console.info(result);
browser.close();
});
});
});
});
That gives me a result with the whitespace intact:
$ node get_user.js
Don Kirkby
top 2% overall
In my real script, I want to extract the text of several nodes, so I need a function instead of a simple string:
const puppeteer = require('puppeteer');
puppeteer.launch().then(function(browser) {
browser.newPage().then(function(page) {
page.goto('https://stackoverflow.com/users/4794').then(function() {
page.evaluate(function() {
return $('h2.user-card-name').text();
}).then(function(result) {
console.info(result);
browser.close();
});
});
});
});
That gives the exact same result. Now I need to add error handling, and maybe reduce the indentation levels.
Using await/async and $eval, the syntax looks like the following:
await page.goto('https://stackoverflow.com/users/4794')
const nameElement = await context.page.$eval('h2.user-card-name', el => el.text())
console.log(nameElement)
I use page.$eval
const text = await page.$eval('h2.user-card-name', el => el.innerText );
console.log(text);
I had success using the following:
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(url);
await page.waitFor(2000);
let html_content = await page.evaluate(el => el.innerHTML, await page.$('.element-class-name'));
console.log(html_content);
} catch (err) {
console.log(err);
}
Hope it helps.