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');
Related
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..
I'm building a Slackbot that makes a call to an Express app, which then needs to 1) fetch some other data from the Slack API, and 2) insert resulting data in my database. I think I have the flow right finally using async await, but the operation is timing out because the original call from the Slackbot needs to receive a response within some fixed time I can't control. It would be fine for my purposes to ping the bot with a response immediately, and then execute the rest of the logic asynchronously. But I'm wondering the best way to set this up.
My Express route looks like:
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
// POST api/slack/foo
router.post('/foo', async (req, res) => {
let [body, images] = await slack.grab_context(req);
knex('texts')
.insert({ body: body,
image_ids: images })
.then(text => { res.send('worked!'); }) // This sends a response back to the original Slackbot call
.catch(err => { res.send(err); })
});
module.exports = router;
And then the slack_helpers module looks like:
const { WebClient } = require('#slack/web-api');
const Slack = new WebClient(process.env.SLACKBOT_TOKEN);
async function grab_context(req) {
try {
const context = await Slack.conversations.history({ // This is the part that takes too long
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
} catch (error) {
return [error.toString(), 'error'];
}
return await parse_context(context);
};
function parse_context(context) {
var body = [];
context.messages.forEach(message => {
body.push(message.text);
});
body = body.join(' \n');
return [body, ''];
}
module.exports = {
grab_context
};
I'm still getting my head around asynchronous programming, so I may be missing something obvious. I think basically something like res.send perhaps needs to come before the grab_context call? But again, not sure the best flow here.
Update
I've also tried this pattern in the API route, but still getting a timeout:
slack.grab_context(req).then((body, images) => {
knex ...
})
Your timeout may not be coming from where you think. From what I see, it is coming from grab_context. Consider the following simplified version of grab_context
async function grab_context_simple() {
try {
const context = { hello: 'world' }
} catch (error) {
return [error.toString(), 'error']
}
return context
}
grab_context_simple() /* => Promise {
<rejected> ReferenceError: context is not defined
...
} */
You are trying to return context outside of the try block where it was defined, so grab_context will reject with a ReferenceError. It's very likely that this error is being swallowed at the moment, so it would seem like it is timing out.
The fix is to move a single line in grab_context
async function grab_context(req) {
try {
const context = await Slack.conversations.history({
channel: req.body.channel_id,
latest: req.headers['X-Slack-Request-Timestamp'],
inclusive: true,
limit: 5
});
return await parse_context(context); // <- moved this
} catch (error) {
return [error.toString(), 'error'];
}
};
I'm wondering the best way to set this up.
You could add a higher level try/catch block to handle errors that arise from the /foo route. You could also improve readability by staying consistent between async/await and promise chains. Below is how you could use async/await with knex, as well as the aforementioned try/catch block
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
const slack = require('../../services/slack_helpers');
const insertInto = table => payload => knex(table).insert(payload)
const onFooRequest = async (req, res) => {
try {
let [body, images] = await slack.grab_context(req);
const text = await insertInto('texts')({
body: body,
image_ids: images,
});
res.send('worked!');
} catch (err) {
res.send(err);
}
}
router.post('/foo', onFooRequest);
module.exports = router;
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();
})
I use Supertest to test my Express apps, but I'm running into a challenge when I want my handlers to do asynchronous processing after a request is sent. Take this code, for example:
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/user', async (req, res) => {
res.status(200).json({ success: true });
await someAsyncTaskThatHappensAfterTheResponse();
});
describe('A Simple Test', () => {
it('should get a valid response', () => {
return request(app)
.get('/user')
.expect(200)
.then(response => {
// Test stuff here.
});
});
});
If the someAsyncTaskThatHappensAfterTheResponse() call throws an error, then the test here is subject to a race condition where it may or may not failed based on that error. Even aside from error handling, it's also difficult to check for side effects if they happen after the response is set. Imagine that you wanted to trigger database updates after sending a response. You wouldn't be able to tell from your test when you should expect that the updates have completely. Is there any way to use Supertest to wait until the handler function has finished executing?
This can not be done easily because supertest acts like a client and you do not have access to the actual req/res objects in express (see https://stackoverflow.com/a/26811414/387094).
As a complete hacky workaround, here is what worked for me.
Create a file which house a callback/promise. For instance, my file test-hack.js looks like so:
let callback = null
export const callbackPromise = () => new Promise((resolve) => {
callback = resolve
})
export default function callWhenComplete () {
if (callback) callback('hack complete')
}
When all processing is complete, call the callback callWhenComplete function. For instance, my middleware looks like so.
import callWhenComplete from './test-hack'
export default function middlewareIpnMyo () {
return async function route (req, res, next) {
res.status(200)
res.send()
// async logic logic
callWhenComplete()
}
}
And finally in your test, await for the callbackPromise like so:
import { callbackPromise } from 'test-hack'
describe('POST /someHack', () => {
it.only('should handle a post request', async () => {
const response = await request
.post('/someHack')
.send({soMuch: 'hackery'})
.expect(200)
const result = await callbackPromise()
// anything below this is executed after callWhenComplete() is
// executed from the route
})
})
Inspired by #travis-stevens, here is a slightly different solution that uses setInterval so you can be sure the promise is set up before you make your supertest call. This also allows tracking requests by id in case you want to use the library for many tests without collisions.
const backgroundResult = {};
export function backgroundListener(id, ms = 1000) {
backgroundResult[id] = false;
return new Promise(resolve => {
// set up interval
const interval = setInterval(isComplete, ms);
// completion logic
function isComplete() {
if (false !== backgroundResult[id]) {
resolve(backgroundResult[id]);
delete backgroundResult[id];
clearInterval(interval);
}
}
});
}
export function backgroundComplete(id, result = true) {
if (id in backgroundResult) {
backgroundResult[id] = result;
}
}
Make a call to get the listener promise BEFORE your supertest.request() call (in this case, using agent).
it('should respond with a 200 but background error for failed async', async function() {
const agent = supertest.agent(app);
const trackingId = 'jds934894d34kdkd';
const bgListener = background.backgroundListener(trackingId);
// post something but include tracking id
await agent
.post('/v1/user')
.field('testTrackingId', trackingId)
.field('name', 'Bob Smith')
.expect(200);
// execute the promise which waits for the completion function to run
const backgroundError = await bgListener;
// should have received an error
assert.equal(backgroundError instanceof Error, true);
});
Your controller should expect the tracking id and pass it to the complete function at the end of controller backgrounded processing. Passing an error as the second value is one way to check the result later, but you can just pass false or whatever you like.
// if background task(s) were successful, promise in test will return true
backgroundComplete(testTrackingId);
// if not successful, promise in test will return this error object
backgroundComplete(testTrackingId, new Error('Failed'));
If anyone has any comments or improvements, that would be appreciated :)
I am struggling with how to do this properly in nodejs. This tries to do two things in parallel:
downloads a webpage using axios
creates a directory
When those are finished:
save result asynchronously to a file in de created directory
Then waits until done
const uuidv1 = require('uuid/v1')
const fs = require('fs')
const util = require('util')
const axios = require('axios')
const path = require('path')
const mkdir = util.promisify(fs.mkdir)
const writeFile = util.promisify(fs.writeFile)
const downloadPage = async (url='http://nodeprogram.com') => {
console.log('downloading ', url)
const fetchPage = async function() {
const folderName = uuidv1()
return axios
.all([
mkdir(folderName),
axios.get(url)
])
.then(axios.spread(function (f, r) {
writeFile(path.join(__dirname, folderName, 'file.html'), r.data);
}));
}
await fetchPage()
}
downloadPage(process.argv[2])
Your question and sample are looking contradictory. Question says, you need to make use of async and await to make parallel calls, but your sample code shows you need sequential calls instead of parallel.
Best use of Async/Awaits are for making sequential calls.
async function is a kind of shorthand function for 'Promise', were things are done implicitly like returning will be considered as 'resolve'.
await should always be within async function, add await on functions you need to wait for before proceeding further.
Syntax change in await function is, instead of
somePromiseFunctionCall().then( (someVarible) => {...}).catch(e => {})
you need to use
const asyncFunction = async (parameters) => {
try {
// First Function call that returns Promise / async
someVariable = await somePromiseFunctionCall();
// Second (sequential) call that returns Promise / async
someNewVariable = await someotherPromiseFunctionCall();
} catch(e) {
throw new Error(e);
}
}
Now, in your sample, if your requirement is to wait for axios to return and then create folder and then write the result to file, that can be done using async and await.
Change this:
writeFile(path.join(__dirname, folderName, 'file.html'), r.data);
to this:
return writeFile(path.join(__dirname, folderName, 'file.html'), r.data);
You need to return the promise from writeFile so it is added to the chain so that the promise you're returning from fetchPage() is linked to the writeFile() operation. As your code was originally, the writeFile() operation is proceeding on it's own and is not connected at all to the promise you were returning from fetchPage() so when you do:
await fetchPage()
it wasn't awaiting the writeFile() operation.
A cleaned up version could look like this:
const downloadPage = (url='http://nodeprogram.com') => {
console.log('downloading ', url)
// don't really need this separate fetchPage() function
const fetchPage = function() {
const folderName = uuidv1()
return axios
.all([
mkdir(folderName),
axios.get(url)
])
.then(axios.spread(function (f, r) {
return writeFile(path.join(__dirname, folderName, 'file.html'), r.data);
}));
}
return fetchPage()
}
Then, you would use it like this:
downloadPage().then(() => {
// page is downloaded now
});
Or, inside an async function, you could do:
await downloadPage();
// page is downloaded here
Note, that I removed several cases of async and await as they weren't needed. await fetchPage() wasn't doing you any good at the end of downloadPage(). From a timing point of view, that does the exact same thing as return fetchPage() and this way, you're actually resolving with the resolved value of fetchPage() which can be more useful. There did not appear to be any reason to use async or await in downloadPage(). Keep in mind that an async function still returns a promise and the caller of that function still has to use .then() or await on the return value from that function. So, using await inside of downloadPage() doesn't change that for the caller.