I am trying to write something to check that "About Us" exist on the following page: https://www.aggrowth.com/en-us/about-us and I am just hitting a wall. It shouldn't be difficult, but I have spent too much time on this.
We are using Gherking-testcafe: https://www.npmjs.com/package/gherkin-testcafe
NPM: 6.9.0
TestCafe: 1.0.1
Gherking-Testcafe: 2.0.0
I tried (All below was tested isolation, aka all of the different t.expect was run by themselves):
const h1AboutUs = await Selector('h1');
await t.expect(h1AboutUs.innerText).eql('About Us');
await t.expect(h1AboutUs.innerText).contains('About Us');
await t.expect(h1AboutUs.value).eql('About Us');
await t.expect(Selector('html').textContent).contains('About Us');
and tried removing the await:
const h1AboutUs = Selector('h1');
await t.expect(h1AboutUs.innerText).eql('About Us');
await t.expect(h1AboutUs.innerText).contains('About Us');
await t.expect(h1AboutUs.value).eql('About Us');
await t.expect(Selector('html').textContent).contains('About Us');
It works if I do:
This is the test I have:
When("I see the page load", async t => {
const h1AboutUs = await Selector('h1');
await t.expect(h1AboutUs.visible).eql(true);
await t.hover(h1AboutUs);
await t.expect(h1AboutUs.value).contains('about');
console.log(h1AboutUs.value);
});
My testCafe runner:
const createTestCafe = require('gherkin-testcafe');
const fs = require('fs');
const reportPath = './frontend/src/tests/test-reports'
let testcafe = null;
function readTestCafeConfig() {
configData = fs.readFileSync('.testcaferc.json', 'utf8');
const js = JSON.parse(configData);
return getJSONValues(js, 'src');
};
function getJSONValues(obj, key) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (i === key) {
objects.push(obj[i]);
}
}
return objects;
}
createTestCafe('localhost', 1337, 1338)
.then(tc => {
testcafe = tc;
const runner = testcafe.createRunner();
const src = readTestCafeConfig();
return runner
.src([src])
.browsers('chrome')
.reporter(['spec', {
name: 'json',
output: `${reportPath}/report/report.json`
},
{
name: 'xunit',
output: `${reportPath}/report/report.xml`
}])
// .video(`${reportPath}/videos`, {
// singleFile: true,
// failedOnly: true,
// pathPattern: '${USERAGENT}/${FILE_INDEX}.mp4'
// })
.tags('#aboutUs')
.run();
})
.then(failedCount => {
console.log('Tests failed: ' + failedCount);
testcafe.close();
});
The error I get in the console is:
1) Selector cannot implicitly resolve the test run in context of which it should be executed. If you need to call Selector from the Node.js API callback, pass the test controller manually via
Selector's `.with({ boundTestRun: t })` method first. Note that you cannot execute Selector outside the test code.
Browser: Chrome 74.0.3729 / Mac OS X 10.14.4
12 |});
13 |
14 |When("I see the page load", async t => {
15 | const h1AboutUs = await Selector('h1');
16 |
> 17 | await t.expect(h1AboutUs.visible).eql(true);
18 | await t.hover(h1AboutUs);
19 | await t.expect(h1AboutUs.value).contains('about');
20 | console.log(h1AboutUs.value);
21 |});
22 |
I expect not to see this error msg
You need to implement Selector binding to TestCafe's test controller for such tests. Please have a look at the following example:
const { Given, Then, Before } = require('cucumber');
const { Selector: NativeSelector } = require('testcafe');
const Selector = (input, t) => {
return NativeSelector(input).with({ boundTestRun: t });
};
Before('#aboutHook', async () => {
console.log('Running AGI test.');
});
Given("I am open AGI page", async t => {
await t.navigateTo('https://www.aggrowth.com/en-us/about-us');
});
Then("I should see check about us", async t => {
const h1AboutUs = Selector('h1', t);
//or const h1AboutUs = await Selector('h1', t); if you need
await t
.expect(h1AboutUs.visible).eql(true)
.hover(h1AboutUs);
});
You can get more examples in the gherkin-testcafe repository.
Note also that the h1 element doesn't have a property value.
You can learn more about TestCafe Selectors and their properties in TestCafe Docs.
Related
I need to open 20 pages parallelly and click on a button then wait for a response after that get the data from a tag. and my code is:
async function getPageData(links) {
return new Promise(async (resolve, reject) => {
try {
const cluster = await Cluster.launch({
concurrency: Cluster.CONCURRENCY_PAGE,
maxConcurrency: 200,
monitor: true,
});
let allData = [];
await cluster.task(async function getData({ page, data: url }) {
await page.goto(url, {
waitUntil: 'networkidle2',
});
const buttonQuery = 'button[role=tab]:first-child';
const buttonElement = await page.waitForSelector(buttonQuery);
await buttonElement.click(buttonElement);
await page.waitForResponse('https://XXX'); // the problem is here
const data = await page.evaluate(getList);
const [oscillators, summary, movingAverage] = data;
allData.push({ oscillators, summary, movingAverage });
});
links.map(async function addQueue(link) {
cluster.queue(link);
});
await cluster.idle();
await cluster.close();
resolve(allData);
} catch (e) {
reject(e);
}
});
but it just work for the first time and ignore the rest of the tasks. but when I remove page.waitForResponse() the all tasks will be run as expected.
How can I make all tasks wait for their response then extract the data?
When I try to capture text from an element inside an iFrame where the element content changes every second, I get "undefined". What might I be doing wrong?
Code:
const { firefox } = require('playwright');
const fs = require('fs');
var url = 'http://jsfiddle.net/6vnam1jr/1/show';
var section_path = 'xpath=/html/body/div/div/div/section';
var iframe_path = 'xpath=/html/body/div/iframe';
var text_path = 'xpath=//html/body/div[2]/div[2]';
async function getElementText(browser,page){
await page.goto(url);
await page.click(section_path);
const frame = await page.$(iframe_path);
const contentFrame = await frame.contentFrame();
await sleep(1000);
let handle = await contentFrame.$eval(text_path);
console.log(handle)
// try again
await sleep(1000);
handle = await contentFrame.$eval(text_path);
console.log(handle)
closeBrowser(browser);
}
async function closeBrowser(browser){
await browser.close();
}
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
(async () => {
const browser = await firefox.launch({ headless: false });
const page = await browser.newPage();
getElementText(browser,page);
})();```
Thanks for the repro. frame.$eval is an API to run a JS function in the browser which takes the element as an argument.
I believe what you are looking for is an ElementHandle to this element. You can use frame.waitForSelector or frame.$ for this purpose. I have verified that they are not undefined.
// ...
let handle = await contentFrame.waitForSelector(text_path);
I've written a cloud task and it works perfectly and triggers the link I gave without any problems, but it won't stop retrying running the link.
How can I make it run it only once?
What I'm trying to do is run a Firestore Function once in the future, on a document write in a collection. I found this tutorial for it.
So far my task creation code works perfectly, and delivers correct payload to the function it's going to call. And the called function works correctly too the first time it runs and exits with status 200. But on the retries I have to exit with error 500 since there's no data to access anymore.
I can see the 200 and 500 logs in firestore function's logs, but Cloud Tasks' logs is empty, even if a method has been run 50 times!
This is the full code
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
const { CloudTasksClient } = require('#google-cloud/tasks')
exports.moveActivityFromPlanToRecord = () =>
functions
.region('europe-west1')
.firestore.document('Users/{userId}/Activities/{activityId}')
.onCreate(async snapshot => {
const moveTime = snapshot.data()! as MoveTime
if (!moveTime || !moveTime.dueTime) {
console.log("DueTime is empty or null: \n" + moveTime)
return
}
// Get the project ID from the FIREBASE_CONFIG env var
const project = JSON.parse(process.env.FIREBASE_CONFIG!).projectId
const location = 'europe-west1'
const queue = 'activityDateEventChecker'
//queuePath is going to be a string that uniquely identifes the task
const tasksClient = new CloudTasksClient()
const queuePath: string =
tasksClient.queuePath(project, location, queue)
// URL to my callback function and the contents of the payload to deliver
const url = `https://${location}-${project}.cloudfunctions.net/activityDateEventCheckerCallback`
const docPath = snapshot.ref.path
const dueTime = moveTime.dueTime
const payload: MoveTaskPayload = { docPath, dueTime }
console.log(payload)
// build up the configuration for the Cloud Task
const task = {
httpRequest: {
httpMethod: 'POST',
url: url,
body: Buffer.from(JSON.stringify(payload)).toString('base64'),
headers: {
'Content-Type': 'application/json',
},
},
scheduleTime: {
seconds: moveTime.dueTime / 1000
}
}
// enqueue the task in the queue
return tasksClient.createTask({ parent: queuePath, task: task })
})
interface MoveTime extends admin.firestore.DocumentData {
dueTime?: number
}
interface MoveTaskPayload {
docPath: string,
dueTime: number
}
exports.activityDateEventCheckerCallback = () =>
functions
.region('europe-west1')
.https.onRequest(async (req, res) => {
const payload = req.body as MoveTaskPayload
try {
// getting the item
const activity = await admin.firestore().doc(payload.docPath).get()
// if time is up for it
if (Date.now() >= payload.dueTime && activity.data() != undefined) {
// getting path to activity to be in record
const pathUser = activity.ref.parent.parent?.path
const pathDocRecord = admin.firestore().doc(`${pathUser}/Record/${activity.id}`)
console.log("RECORD-- ", (await (await pathDocRecord.get()).data())?.subject)
// moving activity into record
await pathDocRecord.set(activity.data()!)
await activity.ref.delete()
// sending notif to user
const fcmPayload = {
notification: {
title: `${activity.data()?.subject}`,
body: " Time for activity. Record how it goes!"
},
data: {
activityId: activity.id
}
}
const user = await admin.firestore().doc(pathUser!).get()
const fcmToken: string = user.data()?.fcmToken
return admin.messaging().sendToDevice(fcmToken, fcmPayload)
}
return null
} catch (error) {
console.error(error)
res.status(500).send(error)
return null
}
})
Tasks in Cloud Task retries when it does not get response code 2XX.
You can config the retry in Cloud Task Queue using maxAttempt paramtere.
Details are mentioned in the doc
I would like to call an asynchronous function outside the lambda handler with by the following code:
var client;
(async () => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
});
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
The 'client' does not get initialized. The same code works perfectly if executed within the handler.
initSecrets contains a lambda invocation of getSecrets() which calls the AWS SecretsManager
Has anyone an idea how asynchronous functions can be properly called for initialization purpose outside the handler?
Thank you very much for your support.
I ran into a similar issue trying to get next-js to work with aws-serverless-express.
I fixed it by doing the below (using typescript so just ignore the :any type bits)
const appModule = require('./App');
let server: any = undefined;
appModule.then((expressApp: any) => {
server = createServer(expressApp, null, binaryMimeTypes);
});
function waitForServer(event: any, context: any){
setImmediate(() => {
if(!server){
waitForServer(event, context);
}else{
proxy(server, event, context);
}
});
}
exports.handler = (event: any, context: any) => {
if(server){
proxy(server, event, context);
}else{
waitForServer(event, context);
}
}
So for your code maybe something like
var client = undefined;
initSecrets("MyWebApi").then(result => {
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET)
})
function waitForClient(){
setImmediate(() => {
if(!client ){
waitForClient();
}else{
client('Request')
}
});
}
exports.handler = async function (event, context) {
if(client){
client('Request')
}else{
waitForClient(event, context);
}
};
client is being called before it has initialised; the client var is being "exported" (and called) before the async function would have completed. When you are calling await client() the client would still be undefined.
edit, try something like this
var client = async which => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
let api = new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
return api(which) // assuming api class is returning a promise
}
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
This can be also be solved with async/await give Node v8+
You can load your configuration in a module like so...
const fetch = require('node-fetch');
module.exports = async () => {
const config = await fetch('https://cdn.jsdelivr.net/gh/GEOLYTIX/public/z2.json');
return await config.json();
}
Then declare a _config outside the handler by require / executing the config module. Your handler must be an async function. _config will be a promise at first which you must await to resolve into the configuration object.
const _config = require('./config')();
module.exports = async (req, res) => {
const config = await _config;
res.send(config);
}
Ideally you want your initialization code to run during the initialization phase and not the invocation phase of the lambda to minimize cold start times. Synchronous code at module level runs at initialization time and AWS recently added top level await support in node14 and newer lambdas: https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/ . Using this you can make the init phase wait for your async initialization code by using top level await like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
console.log("start init");
await sleep(1000);
console.log("end init");
export const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
This works great if you are using ES modules. If for some reason you are stuck using commonjs (e.g. because your tooling like jest or ts-node doesn't yet fully support ES modules) then you can make your commonjs module look like an es module by making it export a Promise that waits on your initialization rather than exporting an object. Like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const main = async () => {
console.log("start init");
await sleep(1000);
console.log("end init");
const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
return { handler };
};
# note we aren't exporting main here, but rather the result
# of calling main() which is a promise resolving to {handler}:
module.exports = main();
I have following sample function from this tutorial: Asynchronous Programming (I Promise!) with Cloud Functions for Firebase - Firecasts
exports.emailEmployeeReport = functions.database
.ref('/employees/${eid}/reports/${rid}')
.onWrite(event => {
const eid = event.params.eid;
const report = event.data.val().report;
const root = event.data.ref.root;
const mgr_promise = root.child(`/employees/${eid}/manager`).once('value');
const then_promise = mgr_promise.then(snap => {
const mgr_id = snap.val();
const email_promise = root.child(`/employees/${mgr_id}/email`).once('value');
return email_promise;
}).catch(reason => {
// Handle the error
console.log(reason);
});;
const then_promise2 = then_promise.then(snap => {
const email = snap.val();
const emailReportPromise = sendReportEmail(email, report);
return emailReportPromise;
}).catch(reason => {
// Handle the error
console.log(reason);
});
return then_promise2;
});
var sendReportEmail = function (email, report) {
const myFirstPromise = new Promise((resolve, reject) => {
// do something asynchronous which eventually calls either:
//
setTimeout(function () {
try {
var someValue = "sendReportEmail";
console.log(someValue);
// fulfilled
resolve(someValue);
}
catch (ex) {
// rejected
reject(ex);
}
}, 2000);
});
return myFirstPromise;
}
once I run firebase deploy command, eventually I am getting following error:
functions[emailEmployeeReport]: Deploy Error: Failed to configure
trigger
providers/google.firebase.database/eventTypes/ref.write#firebaseio.com
(emailEmployeeReport)
I also have a simple hello-world method and a similar trigger method, and they deploy fine.
Am I missing something here?
The syntax for wildcards in the database reference does not have "$".
Try the following:
exports.emailEmployeeReport = functions.database
.ref('/employees/{eid}/reports/{rid}')