I'm implementing Testing Library with Puppeteer and I was trying to use an environment variable, DEBUG_PRINT_LIMIT, to limit the length of the HTML printed to console in case of failure.
But for some reasons, the variable environment is just ignored by the library...
My project:
package.json
{
"scripts": {
"test": "jest --detectOpenHandles"
},
"devDependencies": {
"jest": "^26.6.3",
"pptr-testing-library": "^0.6.4",
"puppeteer": "^2.1.1"
}
}
main.test.js
const puppeteer = require('puppeteer')
require('pptr-testing-library/extend')
test('Should go to the forum', async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://mkpc.malahieude.net");
const $document = await page.getDocument();
const $forum = await $document.getByText("Forum");
});
Now when I run DEBUG_PRINT_LIMIT=10 npm test, the environment variable is just ignored since it prints me the whole HTML instead of just 10 characters...
I tried many things: setting the environment variable in the package.json file, or directly in the code, but nothing works, the variable is just ignored.
However if I change the code of the library (file node_modules/pptr-testing-library/dom-testing-library.js), and I replace process.env.DEBUG_PRINT_LIMIT || 7000 with process.env.DEBUG_PRINT_LIMIT || 10, then it works! So it seems that for some reason the environment variable is not passed correctly to the library.
I'm using node version 12 on a Debian machine (I think it doesn't change anything though).
Can you tell me what I'm doing wrong?
Thanks
If finally figured it out.
It's actually a bug in the library itself: https://github.com/testing-library/pptr-testing-library/issues/55
Related
I have a Node.js Mocha test suite (I've created a minimal reproduction based on the real world application I was trying to create an automated test for).
package.json:
{
"name": "puppeteer-mocha-hang-repro",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"chai": "4.3.7",
"express": "4.18.2",
"mocha": "10.2.0",
"puppeteer": "19.6.2"
}
}
index.spec.js:
const expect = require('chai').expect;
const express = require('express');
const puppeteer = require('puppeteer');
const webServerPort = 3001;
describe('test suite', function () {
this.timeout(10000);
let webServer;
let browser;
beforeEach(async () => {
// Start web server using Express
const app = express();
app.get('/', (_, res) => {
res.send('<html>Hello, World from the <span id="source">Express web server</span>!</html>');
});
webServer = app.listen(webServerPort, () => {
console.log(`Web server listening on port ${webServerPort}.`);
});
// Start browser using Puppeteer
browser = await puppeteer.launch();
console.log('Browser launched.');
});
afterEach(async () => {
// Stop browser
await browser.close();
console.log('Browser closed.');
// Stop web server
await webServer.close();
console.log('Web server closed.');
});
it('should work', async () => {
const page = await browser.newPage();
await page.goto(`http://localhost:${webServerPort}/`);
console.log('Went to root page of web server via Puppeteer.');
if (process.env['PARSE_PAGE'] === 'true') {
const sel = await page.waitForSelector('#source');
const text = await sel.evaluate(el => el.textContent);
console.log('According to Puppeteer, the text content of the #source element on the page is:', text);
expect(text).eql('Express web server');
}
await page.close();
console.log('Page closed.');
});
});
If I run the test suite with the command npx mocha index.spec.js, which causes lines 45-48 to be skipped, the test suite passes and the Mocha process ends quickly:
$ time npx mocha index.spec.js
test suite
Web server listening on port 3001.
Browser launched.
Went to root page of web server via Puppeteer.
Page closed.
✔ should work (70ms)
Browser closed.
Web server closed.
1 passing (231ms)
real 0m0.679s
user 0m0.476s
sys 0m0.159s
Note that it finished in 0.679s.
If I instead run it with the command PARSE_PAGE=true npx mocha index.spec.js, which causes none of my code to be skipped, the tests pass quickly but the process hangs for about 30 seconds:
$ time PARSE_PAGE=true npx mocha index.spec.js
test suite
Web server listening on port 3001.
Browser launched.
Went to root page of web server via Puppeteer.
According to Puppeteer, the text content of the #source element on the page is: Express web server
Page closed.
✔ should work (79ms)
Browser closed.
Web server closed.
1 passing (236ms)
real 0m30.631s
user 0m0.582s
sys 0m0.164s
Note that it finished in 30.631s.
I suspected that this meant I was leaving things open, forgetting to call functions like close. But, I am calling close on the Express web server, Puppeteer browser, and Puppeteer page. I tried calling close on the objects I use when I don't skip any of that code, which are sel and text. But if I try that, I get error messages telling me that those objects have no such functions.
System details:
$ node --version
v18.13.0
$ npm --version
9.4.0
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy
$ uname -r
5.10.16.3-microsoft-standard-WSL
Update: this behavior is a regression fixed by #9612 and deployed as 19.6.3. To fix the problem, upgrade to 19.6.3 (or downgrade to <= 19.6.0 if you're using an older Puppeteer for some reason).
See the original answer below.
I'm able to reproduce the hang, even without Mocha. It seems to be a bug in Puppeteer versions 19.6.1 and 19.6.2. Here's a minimal example:
const puppeteer = require("puppeteer"); // 19.6.1 or 19.6.2
const html = `<!DOCTYPE html><html><body><p>hi</p></body></html>`;
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.setContent(html);
const el = await page.waitForSelector("p");
console.log(await el.evaluate(el => el.textContent));
})()
.catch(err => console.error(err))
.finally(async () => {
await browser?.close();
console.log("browser closed");
});
The culprit is page.waitForSelector, which seems to run its full 30 second default timeout even after resolving, somehow preventing the process from exiting. I've opened issue #9610 on Puppeteer's GitHub repo.
Possible workarounds:
Downgrade to 19.6.0.
Avoid using waitForSelector, since the data you want is in the static HTML (may not apply to your actual page though).
Call with page.waitForSelector("#source", {timeout: 0}) which seems to fix the problem, with the risk of stalling forever if used in a script (not a problem with mocha since the test will time out).
Call with page.waitForSelector("#source", {timeout: 1000}) which reduces the impact of the delay, with the risk of a false positive if the element takes longer than a second to load. This doesn't seem to stack, so if you use a 1-3 second delay across many tests, mocha should exit within a few seconds of all tests completing rather than the sum of all delays across all waitForSelector calls. This isn't practical in most scripts, though.
Run npx mocha --exit index.spec.js. Not recommended--this suppresses the issue.
I'm not sure if the behavior is specific to waitForTimeout or if it may apply to other waitFor-family methods.
As an aside, your server listen and close calls are technically race conditions, so:
await new Promise(resolve =>
webServer = app.listen(webServerPort, () => {
console.log(`Web server listening on port ${webServerPort}.`);
resolve();
})
);
and
await new Promise(resolve => webServer.close(() => resolve()));
System details:
$ node --version
v18.7.0
$ npm --version
9.3.0
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy
$ uname -r
5.15.0-56-generic
I also confirmed the behavior on Windows 10.
I am not sure how much it will be helpful but you can try this:
if (process.env['PARSE_PAGE'] === 'true') {
const sel = await page.waitForSelector('#source');
const text = await page.evaluate(el => el.textContent, sel);
console.log('According to Puppeteer, the text content of the #source element on the page is:', text);
expect(text).eql('Express web server');
}
Also, check for global hooks!
I have a weird issue using prisma seed that I've never faced before and struggling to understand what causes it. The app is hosted in a nextjs full-stack project.
I have the following prisma init script:
const prisma = new PrismaClient();
export default prisma;
When using prisma in my app (next dev), everything works and the queries are being executed.
When I try to run the seed script however, it fails with TypeError: Cannot read properties of undefined (reading 'findFirst')
Here is the seed script:
async function main() {
const existingUser = await db.user.findFirst();
// ...not relevant
}
main()
.then(async () => await db.$disconnect())
.catch(async e => {
console.error(e);
await db.$disconnect();
process.exit(1);
});
package.json prisma section:
"prisma": {
"seed": "ts-node src/server/db/seed.ts",
"schema": "src/server/db/schema.prisma"
},
tsconfig.json ts-node section:
"ts-node": {
"require": ["tsconfig-paths/register"],
"transpileOnly": true,
"compilerOptions": {
"module": "commonjs"
}
},
Printing the prisma client on the seed script returns {}, instead of the actual instance that I can see while printing in dev mode.
Any ideas are welcome, thanks!
A few debugging hours later, I finally found the issue. This code fragment is located in the prisma runtime config:
const dmmfModelKeys = Object.keys(client._baseDmmf.modelMap);
const jsModelKeys = dmmfModelKeys.map(dmmfToJSModelName);
const allKeys = [...new Set(dmmfModelKeys.concat(jsModelKeys))];
I have seen that while the first 2 variables were outputting a result, creating a new Set was actually returning an empty array, instead of the real value.
By default, NextJS uses es5 as a target in tsconfig. ES5 however does not yet have the Set construct, which was causing the problem.
Upgrading to es2015 solved the problem.
I was just expecting some kind of an error, instead of silent fail..
In my build scripts I need to evaluate a Cypress configuration file. I'm using the following script:
let appdata = process.env.LOCALAPPDATA;
let version = `11.0.1`;
let src = `${appdata}/Cypress/Cache/${version}/Cypress/resources/app/node_modules/#packages/data-context/src`;
const DataContext = require(`${src}/DataContext.js`).DataContext;
const ProjectConfigManager = require(`${src}/data/ProjectConfigManager.js`).ProjectConfigManager;
(async() => {
const ctx = new DataContext({
schema: null,
schemaCloud: null,
modeOptions: "run",
appApi: {},
localSettingsApi: {},
authApi: {
} ,
configApi: {
},
projectApi: {
} ,
electronApi: {
} ,
browserApi: {
},
})
let configManager = new ProjectConfigManager({
ctx,
configFile: 'C:\\work\\sample\\sample.config.ts',
projectRoot: 'C:\\work\\sample',
handlers: [],
hasCypressEnvFile: false,
eventRegistrar: null/*new EventRegistrar()*/,
onError: (error) => {},
onInitialConfigLoaded: () => {},
onFinalConfigLoaded: () => Promise.resolve(),
refreshLifecycle: () => Promise.resolve(),
})
configManager.configFilePath = "sample.config.ts"
configManager.setTestingType('e2e')
let cfg = await configManager.getConfigFileContents()
console.log(JSON.stringify(cfg));
})();
It works well for Cypress 10 version.
However, Cypress 11 has introduced some changes that break this script. Though I adjusted the paths, I'm still unable to make it work again.
It currently fails with this error:
Error: Cannot find module 'C:\Users\mbolotov\AppData\Local\Cypress\Cache\11.0.1\Cypress\resources\app\node_modules\graphql\index'. Please verify that the package.json has a valid "main" entry
How can I fix this problem (without making changes to the Cypress installation)?
OR
Is there any other way to evaluate a Cypress configuration file (say from the command line) and obtain its values?
The exact usage is unclear to me, but making some assumptions - a nodejs script in the /scripts folder of the project can compile and resolve the config using the Cypress module API.
It would need a test to run, a "null-test" can be generated from inside the script.
Note, the null-test must conform to the spec pattern of the project (below it's the std .cy.js)
const cypress = require('cypress')
const fs = require('fs')
fs.writeFileSync('../cypress/e2e/null-test.cy.js', 'it("", ()=>{})')
cypress.run({
project: '..',
spec: '../cypress/e2e/null-test.cy.js',
quiet: true
}).then(results => {
if (results.status === 'failed') {
console.log(results)
} else {
console.log(results.config.resolved) // output resolved config
}
})
I haven't attempted to duplicate everything you have in your code, as it's using Cypress internals and not publicly documented.
This may be because of Changelog 11.0.0
We have also massively improved our startup performance by shipping a snapshot of our binary instead of the source files.
Looking inside the cache folder for v10.11.0 (${process.env.CYPRESS_CACHE_FOLDER}/10.11.0/Cypress/resources/app), the /node_modules is fully populated and /node_modules/graphql/index.js exists.
But in v11.0.1 /node_modules/graphql is not fully populated.
I'm trying to build some tests for my bot dialogs. I'm using the same test code (and modified test data) with two different bots with the identical dialog names. As such, the test.js file is the same for both bots. However, when I try to run my tests via Mocha on the second bot, I am getting an Error: DialogSet.add(): Invalid dialog being added. message for each test. This does not happen with my first bot. I even tried replacing the dialog file in the second bot with the one from the (working) first, and I still got the same error. As such I can't find anything different between the bots. I even replaced all of the files in question (the test, the test data/conversation, and the dialog itself) with the files from the first bot and still got the same error. Lastly, all botbuilder packages and other dependencies are the same version between the bots. I'm at a loss here...anyone have any ideas?
Here is the dialog that is being called. I left out the actual dialog steps but that shouldn't be relevant to the issue since all of the Dialog add activity happens in the constructor.
const { TextPrompt, ChoicePrompt, ConfirmPrompt, ChoiceFactory, ComponentDialog, WaterfallDialog, DialogSet, DialogTurnStatus } = require('botbuilder-dialogs');
const { VistaServiceHelper } = require('../helpers/vistaServiceHelper');
const { TrackingServiceHelper } = require('../helpers/trackingServiceHelper');
const { CosmosDbStorage } = require('botbuilder-azure');
const LINE_PROMPT = 'linePrompt';
const ORDER_PROMPT = 'orderPrompt';
const CRITERIA_PROMPT = 'criteriaPrompt';
const SEARCH_CRITERIA = ['GO', 'PO'];
const WATERFALL_DIALOG = 'waterfallDialog';
const CONFIRM_PROMPT = 'confirmPrompt';
// Static texts
const escalateMessage = `Escalation message here`
const msDay = 86400000;
class viewOrderDialog extends ComponentDialog {
constructor(dialogId, userDialogStateAccessor, userState) {
super(dialogId);
this.addDialog(new ChoicePrompt(CRITERIA_PROMPT));
this.addDialog(new TextPrompt(ORDER_PROMPT));
this.addDialog(new TextPrompt(LINE_PROMPT, this.validateLineNumber));
this.addDialog(new ConfirmPrompt(CONFIRM_PROMPT));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.requestOrderNumber.bind(this),
this.selectSearchCriteria.bind(this),
this.displayLineItems.bind(this),
this.displayLineStatus.bind(this),
this.loopStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
this.integrationLog = new CosmosDbStorage({
serviceEndpoint: process.env.ACTUAL_SERVICE_ENDPOINT,
authKey: process.env.ACTUAL_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: 'integration-logs'
});
this.queryData = {};
} // End constructor
I was able to fix this by deleting the botbuilder-testing folder inside the project's node_modules folder and rerunning npm install botbuilder-testing (even though I had already confirmed version in package.json and package-lock.json were showing latest version and had run npm install and npm update).
It appears this did stem from some sort of versioning issue and for whatever reason, only completely deleting the folder and reinstalling fixed it.
You may want to also verify the version of botbuilder inside your package.json file, because all of this packages must be at the same version
Ex:
"botbuilder": "~4.10.3",
"botbuilder-ai": "~4.10.3",
"botbuilder-dialogs": "~4.10.3",
"botbuilder-testing": "~4.10.3",
I Believe the accepted answer does not work in all cases . the correct answer would be to have the same botbuilder and botbuilder-testing versions .
I had the same problem and putting the same versions (or at least not putting a botbuilder-testing version above botbuilder worked)
Example
Here are example versions that work together :
"botbuilder": "~4.13.6",
"botbuilder-dialogs": "~4.13.6",
"botbuilder-testing": "^4.13.6",
Here is a simple example of adding command in nodejs using commander:
'use strict';
const {Command} = require('commander');
const run = () => {
const program = new Command();
console.log('CMD');
program.command('cmd [opts...]')
.action((opts) => {
console.log('OPTS');
});
program.parse(process.argv);
};
run();
In this case everything works fine, but when I'm adding description and options, commander throws an error:
program.command('cmd [opts...]', 'DESCRIPTION', {isDefault: true})
node test-commander.js cmd opts
test-commander-cmd(1) does not exist, try --help
My env:
node v8.9.3
npm 5.3.0
commander 2.12.2
That is the declared behavior of commander. From the npm page under Git-style sub-commands...
When .command() is invoked with a description argument, no .action(callback) should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like git(1) and other popular tools.
The commander will try to search the executables in the directory of the entry script (like ./examples/pm) with the name program-command, like pm-install, pm-search.
So, when you add a description like you have, it'll assume you have another executable file called test-commander-cmd for the sub command.
If commander's behavior is not what you were expecting, I might recommend looking into a package I published, called wily-cli... only if you're not committed to commander, of course ;)
Assuming your code rests in file.js, your example with wily-cli would look like this...
const cli = require('wily-cli');
const run = () => {
cli
.command('cmd [opts...]', 'DESCRIPTION', (options, parameters) => { console.log(parameters.opts); })
.defaultCommand('cmd');
};
run();
// "node file.js option1 option2" will output "[ 'option1', 'option2' ]"