How to use React Testing Library to test for appearance/disappearance of elements in Gutenberg editor using Puppeteer? - puppeteer

I have the following set up:
E2E Test Utils - standard WP Gutenberg e2e testing set that uses Jest and Puppeteer
testing-library/pptr-testing-library - All your favorite user-centric querying functions from #testing-library/react & #testing-library/library available from Puppeteer!
I'm having a problem testing for appearance and disappearance of elements in the GB editor. I'm now aware that extended assertions don't work with Puppeteer. I just can't figure out what to test for. When I run the test in an interactive mode, I can see that everything is going fine, but I don't get the correct result. The result of the query is just an empty object. Here's a snippet of my code:
import {
enablePageDialogAccept,
setBrowserViewport,
createNewPost,
openDocumentSettingsSidebar,
insertBlock,
loginUser,
} from '#wordpress/e2e-test-utils';
import '#testing-library/jest-dom/extend-expect';
import { getDocument, queries, waitFor } from 'pptr-testing-library';
import { clearPuppeteerBrowser } from '../../../lib/helpers/e2eHelpers';
const {
getByRole,
getByLabelText,
getByDisplayValue,
getByText,
queryByLabelText,
findByRole,
findByLabelText,
findByText,
findAllByText,
} = queries;
describe('CVGB Button block', () => {
// Initialize vars within suite's scope
let $document;
let $editorSettings;
let $editorContent;
// Enable page dialog before running any tests
beforeAll(async () => {
await clearPuppeteerBrowser();
await loginUser();
await enablePageDialogAccept();
});
// Wait for creating a new post before running each of the tests
beforeEach(async () => {
await createNewPost();
$document = await getDocument(page);
// we want to look for Sidebar region only
$editorSettings = await getByRole($document, 'region', { name: 'Editor settings' });
// the same for the editor content, so we don't get repeated components from all the document
$editorContent = await getByRole($document, 'region', { name: 'Editor content' });
});
it('should be able to unset a Link when URL is already set', async () => {
await insertBlock('CVGB Button');
await openDocumentSettingsSidebar();
const $linkButton = await findByRole($document, 'button', {
name: 'Link',
});
await $linkButton.click();
const $URLInput = await findByRole($document, 'combobox', {
name: 'URL',
});
await $URLInput.click();
await $URLInput.type('https://www.google.es');
await $URLInput.press('Enter');
await waitFor(() => {
expect(
findByLabelText($document, 'Currently selected', {
exact: false,
})
).toBeInTheDocument();
});
});
});
Here is the error I see:
The complete Node error message below the test reads:
(node:41767) UnhandledPromiseRejectionWarning: Error: Protocol error (Runtime.callFunctionOn): Target closed.
at /Users/my-user/gb-siteapp/public/wp-content/plugins/cv-wp-gb-lib-blocks/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:208:63
at new Promise (<anonymous>)
at CDPSession.send (/Users/my-user/gb-siteapp/public/wp-content/plugins/cv-wp-gb-lib-blocks/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:207:16)
at ExecutionContext._evaluateInternal (/Users/my-user/gb-siteapp/public/wp-content/plugins/cv-wp-gb-lib-blocks/node_modules/puppeteer-core/lib/cjs/puppeteer/common/ExecutionContext.js:200:50)
at ExecutionContext.evaluateHandle (/Users/my-user/gb-siteapp/public/wp-content/plugins/cv-wp-gb-lib-blocks/node_modules/puppeteer-core/lib/cjs/puppeteer/common/ExecutionContext.js:151:21)
at /Users/my-user/gb-siteapp/public/wp-content/plugins/cv-wp-gb-lib-blocks/node_modules/pptr-testing-library/lib/index.ts:101:8
at Generator.next (<anonymous>)
at /Users/my-user/gb-siteapp/public/wp-content/plugins/cv-wp-gb-lib-blocks/node_modules/pptr-testing-library/dist/index.js:8:71
at new Promise (<anonymous>)
at Object.<anonymous>.__awaiter (/Users/my-user/gb-siteapp/public/wp-content/plugins/cv-wp-gb-lib-blocks/node_modules/pptr-testing-library/dist/index.js:4:12)
(node:41767) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 908)
EDIT: In response to Caleb Eby's asnwer
I thought that's what the waitFor() is for… however if I add only the await findByLabelText(), I get an error:
SyntaxError: /Users/my-user/gb-siteapp/public/wp-content/plugins/cv-wp-gb-lib-blocks/blocks/button/test/buttonBlock.e2e.spec.js: Unexpected reserved word 'await'. (126:16)
If I change it to:
await waitFor(async () => {
expect(
await findByLabelText($document, 'Currently selected', {
exact: false,
})
).toBeInTheDocument();
});
It takes a very long time to run the test and ends in error:
expect(received).toBeInTheDocument()
received value must be an HTMLElement or an SVGElement.
Received has type: object
Received has value:
{
"_client": {
"_callbacks": [Map],
"_connection": [Connection],
"_sessionId": "9E13F5776A59D1DD6A177F48A9D1A543",
"_targetType": "page",
"emitter": [Object],
"eventsMap": [Map]
},
"_context": {
"_client": [CDPSession],
"_contextId": 17,
"_world": [DOMWorld]
},
"_disposed": false,
"_frameManager": {
"_client": [CDPSession],
"_contextIdToContext": [Map],
"_frames": [Map],
"_isolatedWorlds": [Set],
"_mainFrame": [Frame],
"_networkManager": [NetworkManager],
"_page": [Page],
"_timeoutSettings": [TimeoutSettings],
"emitter": [Object],
"eventsMap": [Map]
},
"_page": {
"_accessibility": [Accessibility],
"_client": [CDPSession],
"_closed": false,
"_coverage": [Coverage],
"_emulationManager": [EmulationManager],
"_fileChooserInterceptors": [Set],
"_frameManager": [FrameManager],
"_javascriptEnabled": true,
"_keyboard": [Keyboard],
"_mouse": [Mouse],
"_pageBindings": [Map],
"_screenshotTaskQueue": [ScreenshotTaskQueue],
"_target": [Target],
"_timeoutSettings": [TimeoutSettings],
"_touchscreen": [Touchscreen],
"_tracing": [Tracing],
"_viewport": [Object],
"_workers": [Map],
"emitter": [Object],
"eventsMap": [Map]
},
"_remoteObject": {
"className": "HTMLDivElement",
"description": "div.block-editor-link-control__search-item.is-current",
"objectId": "-193265238525024879.17.113",
"subtype": "node",
"type": "object"
}
}
This actually has a possible culprit of a solution, but what is the right way of testing for this (instead of toBeInTheDocument())?

It looks like you are missing an await inside of the expect block, since findByLabelText returns a promise:
expect(
await findByLabelText($document, 'Currently selected', {
exact: false,
})
).toBeInTheDocument();

Related

NestJS: Global exception filter not catching anything thrown from a Kafka-based microservice app

We're using NestJS as our Typescript framework for a microservice based architecture.
Some of our deployments are what we call "Kafka workers", pods that run code that doesn't actually expose any REST endpoints, rather only listens to kafka topics and handles incoming events.
The problem is that the global exception filter configured to hopefully catch any throw exception, does not catch anything (and we end up with nods UnhandledPromiseRejection)
The exception filter is very basically configured like this (following the NestJS docs guidelines):
#Catch()
export class KafkaWorkerExceptionFilter implements ExceptionFilter {
private logger: AppLogger = new AppLogger(KafkaWorkerExceptionFilter.name);
catch(error: Error, host: ArgumentsHost): void {
this.logger.error('Uncaught exception', error);
}
}
Our controller for such workers is configured like this:
#Controller()
export class KafkaWorkerController {
private readonly logger = new AppLogger(KafkaWorkerController.name);
constructor(
) {
this.logger.log('Init');
}
#EventPattern(KafkaTopic.PiiRemoval)
async removePiiForTalent(data: IncomingKafkaMessage): Promise<void> {
await asyncDoSomething();
throw new Error('Business logic failed');
}
}
Now, we expect the global exception filter to catch the error thrown from inside the controller handler function (as well as real errors, thrown from real functions nested inside it for sync/async operations). This does not happen.
Again, following the NestJS docs on implementing such a filter I tried many ways, and combinations of ways to 'register' that filter, with no success:
listing as provider on the top-level module definition { provide: APP_FILTER, useClass: KafkaWorkerExceptionFilter }
using the #UseFilters(KafkaWorkerExceptionFilter) decorator above the controller class
using nest's app.useGlobalFilters(new KafkaWorkerExceptionFilter()); on the main.ts file before/after using app.connectMicroservice(...) with the kafka config
Just as a reference to how we init the app in the "kafka-worker" configuration, here is the main.ts file:
async function bootstrap() {
const app = await NestFactory.create(KafkaWorkerAppModule, {
logger: ['error', 'warn', 'debug', 'log', 'verbose'],
});
app.use(Helmet());
app.useGlobalPipes(
new ValidationPipe({
disableErrorMessages: false,
whitelist: true,
transform: true,
}),
);
const logger: AppLogger = new AppLogger('Bootstrap');
const config: ConfigService = app.get(ConfigService);
app.connectMicroservice({
transport: Transport.KAFKA,
options: {
client: {
clientId: SECRET_VALUE,
brokers: [SECRET_HOST_ADDRESS],
ssl: true,
sasl: SOME_BOOLEAN_VALUE
? {
mechanism: 'plain',
username: SECRET_VALUE,
password: SECRET_VALUE,
}
: undefined,
},
consumer: {
allowAutoTopicCreation: false,
groupId: SECRET_VALUE,
},
},
});
await app.startAllMicroservices();
const port = config.servicePort || 3000;
await app.listen(port, () => {
logger.log(`Kafka Worker listening on port: ${port}`);
logger.log(`Environment: ${config.nodeEnv}`);
});
}
bootstrap();
When using the connectMicroservice() method, you're creating a Hybrid Application.
By default a hybrid application will not inherit global pipes, interceptors, guards and filters configured for the main (HTTP-based) application. To inherit these configuration properties from the main application, set the inheritAppConfig property in the second argument (an optional options object) of the connectMicroservice() call, as follow:
const microservice = app.connectMicroservice({
transport: Transport.TCP
}, { inheritAppConfig: true });
So all you need to do in your case is:
Add the Filter - in ONE of these methods:
As an APP_FILTER to the KafkaWorkerAppModule
As a global filter using app.useGlobalFilters(new KafkaWorkerExceptionFilter()) in main.ts
Use #UseFilters(KafkaWorkerExceptionFilter) on each relevant provider - I would avoid this for global filters
Add the inheritAppConfig option to app.connectMicroservice() in your main.ts:
app.connectMicroservice({
transport: Transport.KAFKA,
options: {
client: {
clientId: SECRET_VALUE,
brokers: [SECRET_HOST_ADDRESS],
ssl: true,
sasl: SOME_BOOLEAN_VALUE
? {
mechanism: 'plain',
username: SECRET_VALUE,
password: SECRET_VALUE,
}
: undefined,
},
consumer: {
allowAutoTopicCreation: false,
groupId: SECRET_VALUE,
},
},
},
{ inheritAppConfig: true }
);

Invalid parameter: JSON must contain an entry for 'default' or 'APNS_SANDBOX'. => APNS Error in Scala (lift framework)

I'm getting APN Invalid parameter: JSON must contain an entry for 'default' or 'APNS_SANDBOX'. the log is here
And the code block is here:
how to fix this? this is built in scala lift framework.
This is the example in javascript. In the same way, we have to use. Need to use cases for
1.APNS_SANDBOX
2.APNS
3.default
// Setup SNS Client
const snsClient = new SNS();
// whatever your full endpoint arn is. (from createPlatformEndpoint)
const endpointArn = 'arn:aws:sns:...';
// any extra data you want passed along with the message
const payload = {
someCustomKey: 'someCustomValue'
};
// send it
snsClient.publish({
TargetArn: endpointArn,
MessageStructure: 'json', // so we can put in a custom payload and message
Message: JSON.stringify({
default: `DEFAULT MESSAGE ${message}`,
APNS_SANDBOX: JSON.stringify({
aps: {
alert: `IOS Sandbox SPECIFIC MESSAGE ${message}`,
},
payload,
}),
APNS: JSON.stringify({
aps: {
alert: `IOS Prod SPECIFIC MESSAGE ${message}`,
},
payload,
}),
}),
}).promise().then(() => {
console.log('Notification sent!');
}).catch((err) => {
console.log('Failed to send with:', err);
});
Used this link for reference

object keys are undefined in if conditional, but inside the if statement I can access it

As the title states, I have a variable which is a javascript object, i'm comparing it with another js object by stringifying them. The problem is that the variable is completely accessible without calling the keys, so these
if(JSON.stringify(response) == JSON.stringify(lastcmd))
if(JSON.stringify(response.id) == JSON.stringify(lastcmd))
work perfectly fine, but accessing lastcmd's id key will cause it to throw undefined.
if(JSON.stringify(response) == JSON.stringify(lastcmd.id))
full code link here
Edit: Here's the JSON
{ "id" : "001", "app": "msgbox", "contents": { "title": "Newpaste", "message": "I'm a edited paste!" } }
Edit2: Here's the code on the post
const { BrowserWindow, app, dialog, ClientRequest } = require("electron");
const axios = require("axios");
const url = require("url");
let win = null;
let lastcmd;
function grabCurrentInstructions(fetchurl) {
return axios
.get(fetchurl)
.then(response => {
// handle success
//console.log(response.data);
return response.data;
})
.catch(function(error) {
// handle error
console.log(error);
});
}
function boot() {
//console.log(process.type);
win = new BrowserWindow({
resizable: true,
show: false,
frame: false
});
win.loadURL(`file://${__dirname}/index.html`);
//Loop everything in here every 10 seconds
var requestLoop = setInterval(getLoop, 4000);
function getLoop() {
grabCurrentInstructions("https://pastebin.com/raw/i9cYsAt1").then(
response => {
//console.log(typeof lastcmd);
//console.log(typeof response);
if (JSON.stringify(response.app) == JSON.stringify(lastcmd.app)) {
console.log(lastcmd.app);
clearInterval(requestLoop);
requestLoop = setInterval(getLoop, 4000);
} else {
lastcmd = response;
switch (response.app) {
case "msgbox":
dialog.showMessageBox(response.contents);
//console.log(lastcmd);
clearInterval(requestLoop);
requestLoop = setInterval(getLoop, 1000);
}
}
}
);
}
}
app.on("ready", boot);
And here's the error:
(node:7036) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'id' of undefined
at grabCurrentInstructions.then.response (C:\Users\The Meme Machine\Desktop\nodejsprojects\electronrat\index.js:42:64)
at process._tickCallback (internal/process/next_tick.js:68:7)
Thanks to user str I saw that my lastcmd was undefined when I ran the comparison the first time, this would break it and thereby loop the same error over and over, by addding
grabCurrentInstructions("https://pastebin.com/raw/i9cYsAt1").then(
response => {
lastcmd = response;
}
);
below this line
win.loadURL(`file://${__dirname}/index.html`);
I made sure that the last command sent while the app was offline wouldn't be executed on launch and fixing my problem at the same time!

Loading some XML into JSON in NodeJS

I am new to NodeJS and trying to figure out how to load some xml data from a REST service and convert it to JSON so I can afterwards load it into my view.
I'm trying to do so querying with Hapi API and loading it into an xml parser then convert it to JSON.
Doing the following seems loading correctly the xml object and when printing it it actually shows me some JSON. Does it mean that I don't need to convert to JSON anymore?
const server = new Hapi.Server();
...
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
Request.get('http://ws.seloger.com/search.xml?&idtt=2&idtypebien=2,1&ci=750056&pxmax=400000&tri=initial&naturebien=1,2&surfacemin=65search.xml?',
function (error, response, body) {
if (error) {
throw error;
}
var parse = require('xml-parser');
var inspect = require('util').inspect;
var obj = parse(body);
console.log(inspect(obj, { colors: true, depth: 4 }));
Note that the JSON displayed is also not what I am looking for, ie. showing the details with attributes, children, etc.:
{ declaration: { attributes: { version: '1.0', encoding: 'UTF-8' } },
root:
{ name: 'recherche',
attributes: {},
children:
[ { name: 'resume',
attributes: {},
children: [],
content: '....' },
However looking for something more like this (might be just a different representation)
So I figured out that my issue seemed to have been related to the parser itself. Using this one I get what I need:
var parseString = require('xml2js').parseString;
parseString(body, function (err, jsonData) {
reply.view('index', { result: body });
});

babel es6 wrong exception location with react

i think this is a babel problem (not completely sure). The errors my javascript console throws are always wrong... No matter where the error occurs in my code it points to my handleFailure(serviceName, error) block... for instance... Calling this.foo(); after hand success occurs or even in i move this.foo(); to my getItemById method.. it always throws an error in the same block... What am i doing wrong in my store....
if i remove the bogus code it works just fine... i would like the error shown to me to reference the bogus code..
this is the error:
AircraftLocationStore.js:40 Server call aircraftLocationRest failed with error: aircraftLocationRest!handleFailure # AircraftLocationStore.js:40(anonymous function) # RestServiceClient.js:20
class AircraftLocationStore extends EventEmitter {
constructor() {
super();
this._populateRestCallStatus = RestCallStatus.NOT_REQUESTED;
this._dataStore = Map({});
this.handleSuccess = this.handleSuccess.bind(this);
this.handleFailure = this.handleFailure.bind(this);
}
populate(){
RestService.fetchData(this.handleSuccess, this.handleFailure, 'aircraftLocationRest');
this._populateRestCallStatus = RestCallStatus.STARTED
}
handleSuccess(serviceName, jsonData){
UT.logMethod(TAG, `${serviceName} succeeded!`)
jsonData.forEach((entity) => {
let tempEntity = AircraftLocationHelper.createFromJson(entity);
this._dataStore = this._dataStore.merge(Map.of(tempEntity.id, tempEntity))
});
UT.log('isMap', Map.isMap(this._dataStore))
this.foo();
this._populateRestCallStatus = RestCallStatus.SUCCESS;
this.emitChange();
}
handleFailure(serviceName, error){
//Utils.logMethod(TAG, 'handleFailure');
this._populateRestCallStatus = RestCallStatus.FAILED
console.error(`Server call ${serviceName} failed with error: ${serviceName}!`)
}
...
export default new AircraftLocationStore();
if i try and change an immutablejs record on my display component in the onChange it tells me this...
just in case i will include the code that handles the callback that ALWAYS throws the error
class RestServiceClient{
/**
* #param successCB
* #param failureCB
* #param endPoint
*/
fetchData(successCB, failureCB, endPoint) {
const serverUrl = BASE_URL + endPoint;
UT.log('serverUrl', serverUrl);
fetch(serverUrl).then(r => r.json())
.then(data => successCB(endPoint, data))
.catch(e => failureCB(endPoint, e.toString()))
}
}
export default new RestServiceClient();
here is my webpack.config
var path = require('path');
var webpack = require('webpack');
module.exports = {
devtool: "source-map",
entry: "./src/index.js",
output: {
path: __dirname + "/build",
filename: "bundle.js"
},
module: {
loaders: [{
test: /\.js$/,
loaders: ['react-hot', 'babel'],
include: path.join(__dirname, 'src'),
exclude: /node_modules/
}]
}
};
the problem appeared to be in my anonymous function that was created by the fat arrows =>
i rewrote:
fetch(serverUrl).then(r => r.json())
.then(data => let foo = data; successCB(endPoint, data))
.catch(e => failureCB(endPoint, e.toString()));
as:
fetch(serverUrl)
.then(
function(response) {
if (response.status !== 200) {
failureCB(endPoint, 'Looks like there was a problem. Status Code: ' + response.status);
}
// Examine the text in the response
response.json().then(function(data) {
successCB(endPoint, data)
});
}
)
.catch(function(err) {
failureCB(endPoint, 'Looks like there was a problem. Status Code: ' + err);
});
and i once again have some meaningfull error messages...