I've having yeoman generator with sub generator.
I need to invoke the sub generator via code and I use the code below which is working, I see that the sub generator is invoked and I got the question in the terminal.
docs:
https://yeoman.io/authoring/integrating-yeoman.html
var yeoman = require('yeoman-environment');
var env = yeoman.createEnv();
env.lookup(function () {
env.run('main:sub',err => {
console.log('done' ,err);
});
});
The sub generator have only one question
prompting() {
const prompts = [
{
name: "app",
message: "which app to generate?",
type: "input",
default: this.props.app,
},
];
...
I want to call it silently, which means to pass the value for app question via code and not using terminal and I try this which doesn't works, (I see the question in the terminal)
env.lookup(function () {
env.run('main:sub',{"app":"nodejs"}, err => {
console.log('done' ,err);
});
});
and also tried this which doesnt works
env.lookup(function () {
env.run('main:sub --app nodejs', err => {
console.log('done' ,err);
});
});
How can I do it ? pass the values using code (maybe like it's done on unit test but this code is not unit test... when the terminal is not invoked)
From the docs im not sure how to pass the values
https://yeoman.io/authoring/integrating-yeoman.html
I've also found this but didn't quite understand how to use it to pass parameter to generator
http://yeoman.github.io/environment/Environment.html#.lookupGenerator
is it possible?
You can just do:
env.lookup(function () {
env.run('main:sub',{"app":"nodejs"}, err => {
console.log('done' ,err);
});
});
and inside the sub sub-generator, you can find the value via this.options.app.
To disable the question prompt, defined when field inside the Question Object like this:
prompting() {
const prompts = [
{
name: "app",
message: "which app to generate?",
type: "input",
default: this.props.app,
when: !this.options.app
},
];
. . .
return this.prompt(prompts).then((props) => {
this.props = props;
this.props.app = this.options.app || this.props.app;
});
}
Related
I'm using Playwright as my automation tool.
I've defined objects in a dedicated JS file like in the following example:
//UserObjects.js :
let userObject1 = {
section1: {
properties1: {
propery1: "property1",
},
properties2: {
property1: "property1",
property2: "property2",
},
},
section2: {
properties1 : {
property1: "property1",
},
},
sharedFunctions : {
propertyFunction : function()
{
// Implementation
},
}
}
I want to use the previously defined object in the test file: test.spec.js:
test.describe('Tests group1',async () => {
test('one', async ({ page }) => {
});
test('two', async ({ page }) => {
});
});
I want to update the same object sequentially as the tests run.
I've tried to use module.exports and try to access the object from the spec file but it treats it as undefined.
Another thing I tried is to create a copy of the object in the code block of test.describe() using :
// test.spec.js:
Before the test.describe(), I've required the object from the file:
const {userObject} = require('../userobjects/UserObjects.js');
And then created a copy of the object, but it still does treats it as undefined:
let user1 = Object.assign({}, userObject);
After performing the following in UserObjects.js :
module.exports.getEmployee = {userObject};
One more question, what practice is better? Accessing the object in the external file or creating a copy inside the test.describe()?
Thanks
Problem:
Trying to call tests within an array of items returned from a Cypress custom command.
Approaches attempted using npm package mocha-each and another test using the forEach function.
Custom Command:
I created a custom Cypress command that returns an array of AppParamsType:
/// <reference types="Cypress" />
import { AppParamsType } from 'support';
declare global {
namespace Cypress {
interface Chainable {
cmdGetAppsParams: () => Chainable<AppParamsType[]>;
}
}
}
export function cmdGetAppsParams() {
const paramsApps: AppParamsType[] = [];
cy.cmdAppKeys()
.then(($appKeys: string[]) => {
cy.wrap($appKeys).each(($appKey: string) => {
cy.cmdProviderAppParams($appKey).then((paramApp: AppParamsType) => {
paramsApps.push(paramApp);
});
});
})
.then(() => {
return cy.wrap(paramsApps);
});
}
Cypress.Commands.add('cmdGetAppsParams', cmdGetAppsParams);
Test using Custom Command:
The following Cypress test calls the custom command cmdGetAppsParams() to return an array of items.
The array is being iterated with one test using npm package mocha-each and another test using Array forEach. Neither approach calls the tests within the loops.
import * as forEach from 'mocha-each';
let apps: AppParamsType[];
describe('DESCRIBE Apps Add Apps Spec', () => {
before('BEFORE', () => {
cy.cmdGetAppsParams().then(($apps: AppParamsType[]) => {
expect($apps).to.be.an('array').not.empty;
apps = $apps;
});
});
it('TEST Apps Params Array', () => {
cy.task('log', { line: 'A', data: apps });
expect(apps).to.be.an('array').not.empty;
});
it('TEST each item mocha forEach', () => {
cy.task('log', { line: 'B', data: apps });
forEach(apps).it('item', (item: AppParamsType) => {
cy.task('log', { line: 'B.1', data: item });
expect(item).to.be.an('object').not.null;
});
});
it('TEST each item array forEach', () => {
cy.task('log', { line:'C', data: apps });
expect(apps).to.be.an('array').not.empty;
apps.forEach((item: AppParamsType) => {
it('TEST App Param', () => {
cy.task('log', { line: 'C.1', data: item });
expect(item).to.be.an('object').not.null;
});
});
});
The results I am seeing is that the outer tests, indicated by labels 'A', 'B' and 'C', are getting called. But, not the inner tests, which would be indicated by labels 'B.1' and 'C.1':
{
"line": "A",
"data": [
***
]
}
{
"line": "B",
"data": [
***
]
}
{
"line": "C",
"data": [
***
]
}
Nesting an it() inside an it() looks novel. I'm surprised you are not getting an error from it.
The basic problem when generating tests dynamically is that the Cypress runner needs to know exactly how many tests will be generated before it starts running them. But any Cypress commands (including custom commands) will not run until the entire test script has finished running (excluding callback code), so you can't get the apps list from a custom command.
The best way to proceed is to convert cy.cmdAppKeys(), cy.cmdGetAppsParams(), and cy.cmdProviderAppParams() from custom commands to a plain javascript function, and then run that function at the top of the script, e.g
const apps = getMyApps(); // synchronous JS function,
// will run as soon as the test starts
apps.forEach((item: AppParamsType) => {
const titleForTest = `Test App Param ${item.name}`; // Construct an informative title
it(titleForTest, () => {
...
})
})
If you can provide details of the custom commands cy.cmdAppKeys() and cy.cmdProviderAppParams(), may be able to help convert to the synchronous function.
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I'm new to javascript and especially node and would appreciate some help.
I have the following code which looks up delivery status for various packages. Everything works fine except the final step, which is writing the results to a CSV file using the csv-writer package from npm.
I process each line of the source CSV and populate an array with the results. The array is declared at the top of the file as a const (final_result) outside of any function. In my understanding, this means it can be accessed by all the functions in the code. Is this right?
The problem I'm having is that when I get to the final step (writing the results to a new CSV file), the final_result length is 0 and contains no data !
When I console-log in the first and second functions, there is data showing.I can also see the array being populated as each row is processed
What am I missing or is there a better way (best practice? ) to achieve this. Thanks.
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
const yt = require('yodel-tracking');
const csv = require('csv-parser');
const fs = require('fs');
const final_result = []; // This is showing as empty at the end ?!
const csvWriter = createCsvWriter({
path: 'out.csv',
header: [
{ id: 'Location', title: 'Location' },
{ id: 'Ref', title: 'Ref' },
{ id: 'Scan', title: 'Scan' },
{ id: 'Status', title: 'Status' }
]
});
//Functions in order of how they are called
//1 of 3
function run() {
fs.createReadStream('yodeldata.csv')
.on('error', () => console.log('error'))
.pipe(csv())
.on('data', row => {
output(row); //calling function 2 of 3
});
}
//2 of 3
function output(row) {
yt(row['REF'], 'GB', function(err, result) {
let data = result.parcel_tracking.parcel_status[0];
if (!err) {
let obj = {
Ref: `${row['REF']}`,
Location: `${data.location}`,
Scan: `${data.scan_date} ${data.scan_time}`,
Status: `${data.status_description}`
};
final_result.push(obj); //Pushing each result to the array here
console.log(final_result.length); // I can see the length increasing sequentially here
}
});
}
//3 of 3
function resultToCsv() {
console.log('Number of records to be written to csv: ' + final_result.length); // Why is this Zero ?? Expected an array of objects here
csvWriter
.writeRecords(final_result)
.then(() => console.log('The CSV file was written successfully'));
}
//Function calls
run();
resultToCsv();
This issue here is that when you do this:
run();
resultToCsv();
Those two functions are being called immediately. Because run() is using asynchronous methods, it won't have finished reading the stream before resultToCsv() is called. So you want to wait for the stream to be done being read before calling that.
function run() {
fs.createReadStream('yodeldata.csv')
.on('error', () => console.log('error'))
.pipe(csv())
.on('data', row => {
output(row); //calling function 2 of 3
})
.on('close', resultToCsv);
}
I have an array of objects, each one is a file with properties name, path, extension, and so on, like this:
module.exports = {
logpath: "C:\\",
logsfiles: [
{
name: "log1", // file name
path: this.logpath, // path to log
extension: ".log", // log extension
type: "server", // component type (server, connector-hub, connector-component, gateway)
licensed: true, // boolean for license holder
iis: false, // boolean for iis exposure
application: "N/A" // solution
},
{
name: "log2", // file name
path: this.logpath, // path to log
extension: ".log", // log extension
type: "server", // component type (server, connector-hub, connector-component, gateway)
licensed: true, // boolean for license holder
iis: false, // boolean for iis exposure
application: "N/A" // solution
}
]
}
And I need to iterate through this list by reading the entire file, search for a specific string and, if this string exists, store some of the file properties into an array.
What I have so far is this:
function getFile(log) {
return new Promise((resolve, reject) => {
fs.readFile(
logConfig.logpath + log.name + log.extension,
"utf8",
(err, data) => {
if (err) {
console.log(`Error reading file ${log.name}`);
reject(err);
} else {
if (data.indexOf("String pattern to search") != -1)
resolve({ name: log.name, componentkey: "TODO" });
}
}
);
});
}
I know this piece of code is working if I call it standalone. But if I try to call it inside of a loop like this:
async function getAllFiles(logs) {
const finalArray = [];
const promises = logs.map(async log => await getFile(log));
const results = await Promise.all(promises);
finalArray.push(results);
console.log(finalArray); //not printing
console.log("Done"); //not printing
}
Nothing happens... The last two prints doesn't show on the console...
Can anyone help me by showing me what am I doing wrong?
Noob with promises here, sorry... :) And many thanks in advance!
Ah! Got it!
Stupid!
The getFile(log) return promise was not resolving all of the items since I didn't had an else statement to the if (data.indexOf("String pattern to search") != -1).
Covered that and now I get results!
Thank you!
I'm trying to figure out how to do a recursive prompt with a yeoman generator using promises. I am trying to produce a form generator that will first ask for a name for the form component and then ask for a name (that will be used as an id) for each input (ie: firstName, lastName, username, etc.). I've found answers for this question using callbacks but I would like to stick with promises. Below is the code I have so far and what I am attempting to do for the recursion but is not working. Any help and advice is appreciated thank you in advance!
const Generator = require('yeoman-generator')
const questions = [
{ type: 'input',
name: 'name',
message: 'What is the name of this form?',
default: 'someForm'
},
{
type: 'input',
name: 'input',
message: 'What is the name of the input?'
},
{
type: 'confirm',
name: 'askAgain',
message: 'Is there another input to add?'
}
]
module.exports = class extends Generator {
prompting() {
return this.prompt(questions).then((answers) => {
if (answers.askAgain) {
this.prompting()
}
this.userOptions = answers
this.log(this.userOptions)
})
}
}
For anyone that stumbles across this post looking for an answer this is what I ended up doing to make it work. As you can see in my Form class that extends Generator I have a method called prompting() in there. This is a method recognized by Yeoman's loop as a priority and it will not leave this method until something is returned. Since I'm returning a promise it will wait until my promise is finished before moving on. For my first prompt that's exactly what I need but for the second one to happen in prompting2 you can add
const done = this.async()
at the start of your method. This tells yeoman that you are going to have some asynchronous code happen and not to move past the method containing this until done is executed. If you do not use this and have another priority method in your class after it, such as writing() for when you are ready to produce your generated code, then yeoman will move past your method without waiting for your asynchronous code to finish. And you can see in my method prompting2() that I recursively call it whenever the user states that there is another input to name and it will continue doing so until they say there is not another input to name. I'm sure there is a better way to do this but it is working great for me this way. I hope this helps anyone that is looking for a way to do this!
const Generator = require('yeoman-generator')
const questions = [
{ type: 'input',
name: 'name',
message: 'What is the name of this form?',
default: 'someForm'
}
]
const names = [
{
type: 'input',
name: 'inputs',
message: 'What is the name of the input?',
default: '.input'
},
{
type: 'confirm',
name: 'another',
message: "Is there another input?",
default: true
}
]
const inputs = []
class Form extends Generator {
prompting() {
return this.prompt(questions).then((answers) => {
this.formName = answers.name
this.log(this.formName)
})
}
prompting2 () {
const done = this.async()
return this.prompt(names).then((answers) => {
inputs.push(answers.inputs)
if (answers.another) this.prompting2()
else {
this.inputs = inputs
this.log(this.inputs)
done()
}
})
}
}
module.exports = Form