BackstopJS - Set common selector for all scenarios - backstop.js

I'm using BackstopJS to run some visual regression tests on some React components. I have all of my components displayed on individual Storybook pages inside a "common" wrapper.
For example, each story in Storybook is set up to display the following:
<div key="my_unique_key" id="component_preview">
<MyReactComponentHere />
</div>
Since all of my components are displayed on separate pages in isolation inside that common container with the ID component_preview, I'd like to set a selector in BackstopJS for all test suites so as this is the focus of the screen capture for each test (ie. this is so as I avoid capturing any markdown or prop tables displayed with the component on each page).
I know I can individually set this in each scenario as follows:
scenarios: [
{
...
selectors: [
'div[id="component_preview"]'
],
...
}
],
But given I may have a large number of scenarios (this is an ever growing project so I've no idea as to how many components I will want to capture in isolation in the future), I'd like to be able set this as a general rule for all scenarios and not have to individually set this for every individual scenario.
I've tried setting a selectors array outside of the scenarios configuration but it didn't have any effect.
Is it possible to set a common selector like this for all scenarios without having to set it individually on each scenario?
It's no big deal if I have to set this individually on each scenario (just means more work / duplication of the same configuration) but I'd like to avoid doing that if possible.

OK so I've been doing some work on this and have come up with this solution, which works for what I want, without the need to set a common selector to be captured with every single scenario.
The initial goal was to capture my React components, displayed on Storybook, in isolation (ie. without the markdown or prop tables getting in the way).
Just for everyones reference, these are the relevant dependencies and versions that I'm using (copied and pasted from my projects package.json file):
"#storybook/addon-actions": "^3.4.8",
"#storybook/addon-info": "^3.4.8",
"#storybook/addon-links": "^3.4.8",
"#storybook/addon-options": "^3.4.8",
"#storybook/addons": "^3.4.8",
"#storybook/react": "^3.4.8",
"backstopjs": "^3.2.19",
"prop-types": "^15.6.2",
"react": "^16.4.1",
"react-dom": "^16.4.1"
As a further note, I'm using puppeteer with backstopjs.
The first issue I had to get around was the fact that Storybook displays your component, markdown and prop-tables inside an inner <iframe> element on each page. This caused an issue with backstopjs since the CSS scope had no concept of the inner document inside that inner <iframe>. If my component was bigger than what was visible in the immediate UI then it wouldn't realise that the inner document was longer than the outer one. In addition to this, I wasn't able to set any hideSelectors or removeSelectors for any components inside that inner <iframe> since it was out of scope.
So the first major discovery that helped to isolate that inner <iframe> on its own page was to add iframe.html to the URL as follows (for example - suppose you're running Storybook on your localhost at the default port):
http://localhost:6006/iframe.html?selectedKind=...
This isolates that previously inner <iframe> on its own page without the left menu panel appearing. So, from here, I could now hide and remove selectors as I wanted to since everything was now in scope. The Storybook markdown and prop-tables that are displayed on the page are, conveniently, inside a single <div> element. The unique CSS selector that I used to point to this <div> element is as follows:
div[id="root"] > div > div > div[style*="font-family: -apple-system"]
So what I decided to do, instead of setting up a common selector to be captured with each scenario, was to invoke a common onReadyScript in my backstop.json configuration file as follows:
{
"id": "suite_name",
"viewports": [
...
],
"onReadyScript": "my-on-ready-script.js",
"scenarios": [
...
],
...
}
My script, then, was set up to remove the markdown and prop-tables <div> element as follows:
module.exports = async function (puppeteer) {
/* Remove the markdown and prop tables from the Storybook preview panel */
await puppeteer
.$eval('div[id="root"] > div > div > div[style*="font-family: -apple-system"]', (markdownAndPropTables) => {
markdownAndPropTables.parentNode.remove();
});
};
This leaves my component displayed completely in isolation on each page and backstopjs, then, can capture that component all on its own.
This is the best solution I've been able to find to achieve my goals with this. I'm putting this out there as a potential solution for everyone else, too. Hopefully there's something in this that will help someone else out there looking to do the same thing that I wanted to!

Related

Stencil js: update DOM element outside the component

I'm in the process of migrating some legacy pages to web components using StencilJS, so I'm in a situation where some elements are already handled with StencilJS, some are not, and migrating everything will take quite some time.
In this context I need to be able to update the contents of a target div not managed with StencilJS from a StencilJS component. This div is in a totally different branch of the DOM and it's impossible to move it into the component without rethinking the entire page.
So from my component I need to be able to do something like this:
render() {
const target = document.getElementById(this.targetDiv);
if (target) {
target.innerHTML = jsxToString(this.renderDivContents()) // obviously this doesn't work
}
}
renderDivContents() {
return (<p>Some JSX stuff</p>)
}
So, in other words I need to compile the JSX template immediately into a string. I'm not sure how to do that with StencilJS and if it is even possible. I'm under the impression that there is a way to achieve that because it looks very similar to what we do in tests, but all the resources I find on the topic are for JSX with React and does not really help with StencilJS
If this is not the correct approach, what do you suggest? I know injecting HTML into a DOM element is not ideal, but I'm just trying to find a temporary solution to be able to release my changes gradually.
PS: I know I can also use an overlay approach (generate a div into the component and give it the same position and size than the target div), but this sounds even uglier than innerHTML. This is not the answer I expect.

How to get rid of Mismatching childNodes vs. VNodes in NuxtJs [duplicate]

I am using Nuxt.js / Vuejs for my app, and I keep facing this error in different places:
The client-side rendered virtual DOM tree is not matching server-rendered content.
This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>.
Bailing hydration and performing full client-side render.
I would like to understand what is the best way to debug this error? Is their a way I can record/get the virtual DOM tree for client and server so I could compare and find where the error lies?
Mine is a large application and manually verifying is difficult.
Partial answer: with Chrome DevTools, you can localize the issue and see exactly what element caused the issue. Do the following (I did that with Nuxt 5.6.0 and Chrome 64.0.3282.186)
Show DevTools in Chrome (F12)
Load the page that causes "the client-side rendered virtual DOM tree..." warning.
Scroll to the warning in DevTools console.
Click at the source location hyperlink of the warning (in my case it was vue.runtime.esm.js:574).
Set a breakpoint there (left-clicking at line number in the source code browser).
Make the same warning to appear again. I'm not saying it is always possible, but in my case I simply reloaded the page. If there are many warnings, you can check the message by moving a mouse over msg variable.
When you found your message and stopped on a breakpoint, look at the call stack. Click one frame down to call to "patch" to open its source. Hover mouse over hydrate function call 4 lines above the execution line in patch. Hyperlink to the source of hydrate would open.
In the hydrate function, move about 15 lines from the start and set a breakpoint where false is returned after assertNodeMatch returned false. Set the breakpoint there and remove all other breakpoints.
Make the same warning to happen again. Now, when breakpoint is hit, execution should stop in the hydrate function. Switch to DevTools console and evaluate elm and then vnode. Here elm seem to be a server-rendered DOM element while vnode is a virtual DOM node. Elm is printed as HTML so you can figure out where the error happened.
For me this error happened cuz get Array list in AsyncData and rendered <tr> tags by v-for, i put v-for codes in <client-only> blocks and problem solved
This error can be really painfull to debug. In order to quickly get the element causing an issue edit node_modules/vue/dist/vue.esm.js and add the following lines :
// Search for this line:
function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
var i;
var tag = vnode.tag;
var data = vnode.data;
var children = vnode.children;
inVPre = inVPre || (data && data.pre);
vnode.elm = elm;
// Add the following lines:
console.log('elm', elm)
console.log('vnode', vnode)
console.log('inVpre', inVPre)
// ...
You will get in the console the failing node.
There are a lot of ways of fixing this issue, but most of them are not actual fixes, just hacky band-aids. To note a few:
wrap it into <client-only> tags, beware of some important details tho
using a v-show instead of a v-if
trying to hack some lifecycles
etc...
I highly recommend reading this gorgeous article written by Alexander Lichter
https://blog.lichter.io/posts/vue-hydration-error/
He'll explain you that you should diagnose why this happens and fix the actual issue.
Basically each time something is different from what was generated on the server and what is available when done hydrating on the client will cause this error.
Some of which are:
invalid HTML (having a block element inside of a <p>, same goes for an a tag nested into another, etc...)
3rd party scripts messing around with your components
different state on server vs client
any random is risky (new Date() for example)
any page related to authentication
I highly recommend reading the article to understand in Alexandre's own words how to handle this kind of issue. If you're in a hurry you could always use one band-aid fix but try to actually fix the issue for the best performance and to keep the code clean.
I had the same issue as of nuxt version 2.14.0 while implementing vue-particles package. The fix was to surround the tags with no-ssr and it fixed the issue.
EDIT:
Updated variant of the solution (if Nuxt version is above 2.9.0)
<client-only>
<vue-particles>
</vue-particles>
</client-only>
Old solution:
<no-ssr>
<vue-particles>
</vue-particles>
</no-ssr>
Thanks to budden73's answer, I did a little improvement on the debug process.
Open dev tool
click on the warn message, and click on the first line of the warn message, you will be directed to the Sources panel, with a file name vue.runtime.esm.js?xxxx
ctrl+f to search the above file for assertNodeMatch, not the function, but like:
if (process.env.NODE_ENV !== 'production') {
if (!assertNodeMatch(elm, vnode, inVPre)) {
return false
}
}
Add a break point at the line return false
Refresh the page, and the breakpoint will be triggered.
At the right side of the Sources panel, Under Scope->Local, click on the elm element, you will be directed back to the Elements panel.
The above element is the client side rendered element, compare with your code to see the difference.
If you can't find the source of the bug, the brutal way to fix it is using nuxt's <client-only> tag.
Another likely brutal way is described here. Add an isHydrate variable which default is false, set to true in mounted hook, and render the element after the variable set to true.
For Nuxt version above 2.10 it doesn't need to install nothing, just use the default component <client-only> as mentioned https://nuxtjs.org/api/components-client-only/.
Check the previous warning:
In "nuxt": "^2.12.2", You can spot the cause easily from the previous warning.
In my case:
Incorrect
<nuxt-link to="/game42day">
<a>Game For Today</a>
</nuxt-link>
Correct:
<nuxt-link to="/game42day">
Game For Today
</nuxt-link>
If you're rendering a component conditionally with v-if, then you have two options to solve the problem:
The first one is wrapping the element in <no-ssr></no-ssr> tag.
The second approach is replacing v-if with v-show, here is the link to Vue docs.
Turns out, in my case, I had HTML comment tags , which was causing this stupid, annoying error. Took me too long to figure it out but in case it helps someone.
In my case I had to change this:
<v-expansion-panel-header v-text="name" />
to this:
<v-expansion-panel-header>{{ name }}</v-expansion-panel-header>
I also get many errors due to this problem. I list two cases I often encounter, hope can help you.
With vuetify button, when you create a common component, you should use: <v-btn>{{text}}</v-btn>. Example:
<template>
<v-btn
:width="width"
:color="color"
:class="[rounded ? 'rounded-pill' : 'rounded-lg',textColor]"
v-on:click="onClick"
elevation="0"
:outlined="outlined"
:type="type"
:name="name"
:form="form"
:disabled="disabled"
v-bind="$attrs"
>{{ text }}</v-btn>
</template>
Don't use v-html with <p> tag.
Not use: <p v-html='html'></p>.
Use: <div v-html='html'></div>.
Besides, if you use <client-only></client-only>, this problem is definitely solved, but if you need to SEO page or show google ads, it is not good solution.
Ok this is going to sound silly. I tried a bunch of different solutions for about 15 mins such as restarting the server and deleting the .nuxt directory but I was too lazy to use #budden73's big brain solution. What ended up working for me was simply restarting my computer, give it a shot.
What I have found so far from observation is that when you are using third party packages like jQuery (specially), they sometimes inject html tags into the dom. So Vue/Nuxt looses track of the dom tree and starts complaining.
I was having the same problem and after a while I removed all jQuery and replaced jQuery functionality with Vuejs and those error were all gone.
See here for an example of how to deal with integrations (e.g. Google Analytics or FB Pixel) that modify the DOM. Basically create a plugin and exclude from SSR.
https://nuxtjs.org/faq/ga
What about:
extend (config, ctx) {
config.resolve.symlinks = false
}
See this [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content ( Nuxt / Vue / lerna monorepo )
Now that you found the code causing the problem, the first thing you should do is to verify that your markup (possibly coming from an API) is valid. Code like <p><p>Text</p></p> is not valid because a p element doesn’t allow other block elements (like a paragraph tag) inside.
Be aware, that tags are not allowed to have block level elements like <div> or <p> as children. These <span> tags are used default tag for Vue’s transitions though. You can change that though via <Transition tag="div">.
Check if have used any block-level element inside the inline element.
for example: inside , inside
If you have used an HTML table make sure you have used the tag
In my case, I changed my codes from
<p v-html="$md.render(post.content)"></p>
to
<p>{{ $md.render(post.content) }}</p>
In my case this problem was caused by markdownit module, I solved it by changing the html markup used with v-html. I was with <p> at the beginning and I ended with <div>.
I have some <p> in my v-html render (with $md.render()) so take care if you have same problems with different markups.

How to properly override a default spartacus Component

For the most part overriding an existing Spartacus works fine.
The Minimum amount of effort is
B2cStorefrontModule.withConfig({
cmsComponents: {
DefaultComponent: {
component: CustomComponent
}
},
Mostly I do this because I need to apply some changes to the components html.
So I copy and paste it to my generated customComponent.
But most of the times this html contains directives and pipes that require adding additional Modules to my app.modules.
Sometimes this works fine, but in many other cases the html just wont render.
Example: SearchBoxComponent
when trying to use the html I get a browser Error:
Error: Template parse errors:
The pipe 'cxHighlight' could not be found ("
<a
*ngFor="let suggestion of result.suggestions"
[innerHTML]="[ERROR ->]suggestion | cxHighlight: searchInput.value"
[routerLink]="
{
"): ng:///AppModule/EmvSearchBoxComponent.html#49:21
In other cases I was able to fix this by including the corresponding module that references this pipe "cxHighlight". In this case "SearchBoxModule". But still no good.
How do make this pipe available in my project?
Or maybe there is an even better way around all this importing pain?
You are correct in the way you are overriding components in Spartacus. Like you noticed certain components contain pipes so you will have to manually import those in your components module. This is just the Angular way.
Most of our pipes are reused in multiple components so we try to make them have their own modules to make imports easier.
You are correct about cxHighlight, there is a bug. The pipe is declared in the SearchBoxModule but not exported so it's pretty much impossible to import. I will open an issue to have it fixed.

Server Side Rendering Vue with ASP.NET Core 2

I'm trying to understand the usage and limitations of server side rendering with vuejs when using aspnet core.
I used this starter kit for aspnet core and vuejs to setup a simple vue site, which is running based on the code here: https://github.com/selaromdotnet/aspnet-vue-ssr-test/tree/master
I then modified the project to update the aspnet-prerendering and added vue-server-renderer, compiling a hodgepodge of sources to cobble together this update: https://github.com/selaromdotnet/aspnet-vue-ssr-test/tree/ssr
If I run this project, the site appears to load fine, and if I turn off the javascript in the browser, I can see that it does appear that the server-side rendering executed and populated the html result:
however, because JavaScript is disabled, the content isn't moved into the dom as it looks like it is trying to...
My understanding of server-side rendering is that it would populate the html entirely and serve a completed page to the user, so that even if JS was disabled, they'd at least be able to see the page (specifically for SEO purposes). Am I incorrect?
Now I believe modern search engines will execute simple scripts like this to get the content, but I still don't want a blank page rendered if js is disabled...
Is this a limitation of server-side rendering, or perhaps specifically ssr with vue and/or aspnet core?
or am I just missing a step somewhere?
Edit: more information
I looked at the source code for what I believe is the method that prerenders the section here: https://github.com/aspnet/JavaScriptServices/blob/dev/src/Microsoft.AspNetCore.SpaServices/Prerendering/PrerenderTagHelper.cs
The line
output.Content.SetHtmlContent(result.Html);
has a null value for result.Html. However, when I manually edit this value to put a test value, it also doesn't render to the output html, and the app div tag is still empty...
If I'm doing something wrong to populate the result.Html value with the expected output, that's one thing, and I would appreciate some help in doing that, especially since the output html appears to be found, since it's in the script that immediately follows...
However, even if I were to populate it, it appears it's being skipped, as evidenced by me manually changing the value. is this a bug in the code or am I doing somethigng wrong, or perhaps both?
As you correctly noticed, for your project, result.Html inside the tag helper is null. So that line cannot be the location where the output is being generated. Since the HTML output from your prerendering script also does not include a script tag, it is clear that something has to generate that. The only other line that could possible do this is the following from the PrerenderTagHelper:
output.PostElement.SetHtmlContent($"<script>{globalsScript}</script>");
That would fit the observed output, so we should figure out where the globalsScript comes from.
If you look at the PrerenderTagHelper implementation, you can see that it will call Prerenderer.RenderToString which returns a RenderToStringResult. This result object is deserialized from JSON after calling your Node script.
So there are two properties of interest here: Html, and Globals. The former is responsible for containing the HTML output that finally gets rendered inside the tag helper. The latter is a JSON object containing additional global variables that should be set for the client side. These are what will be rendered inside that script tag.
If you look at the rendered HTML from your project, you can see that there are two globals: window.html and window.__INITIAL_STATE__. So these two are set somewhere in your code, although html shouldn’t be a global.
The culprit is the renderOnServer.js file:
vue_renderer.renderToString(context, (err, _html) => {
if (err) { reject(err.message) }
resolve({
globals: {
html: _html,
__INITIAL_STATE__: context.state
}
})
})
As you can see, this will resolve the result containing just a globals object with both html and __INITIAL_STATE__ properties. That’s what gets rendered inside of the script tag.
But what you want to do instead is have html not as part of globals but on the layer above, so that it gets deserialized into the RenderToStringResult.Html property:
resolve({
html: _html,
globals: {
__INITIAL_STATE__: context.state
}
})
If you do it like that, your project will properly perform server-side rendering, without requiring JavaScript for the initial view.

Can there be more than one DOM object in the Cycle.js drivers?

All the Cycle.js examples I've found so far, use a single DOM object, named "DOM", in the drivers argument to run(main, drivers). Is it possible to have more than one object, e.g., one named "DOM1" and another "DOM2"? The purpose of this would be to control two separate dynamic DOM areas within a single HTML page, in order to keep a third DOM area statically defined in index.html, and sandwiched between DOM1 and DOM2.
As a side question, the examples I've seen typically target an HTML div with an id of #app or #main-container, and then the sink is defined with a #cycle/dom div function, thus creating AFAICT an unnecessary div within a div. I haven't found a clear explanation or reference of how the virtual nodes are supposed to be defined. Say that DOM2 above targets an HTML form element and that is supposed to contain two input elements. Does it have to start with a div as in all the examples, or can the inputs be defined directly in the .map call, and if so, how?
There is nothing preventing you from having a DOM1 and DOM2 sinks in your app. bloodyKnuckles' example illustrate that perfectly https://esnextb.in/?gist=b54baa4131974b7f12d190fb63be8aeb
That being said, I am not sure I really see the point of doing this. If it's a matter of performance, I don't think you will gain much in splitting the rendering of your app into two DOMDrivers. The virtual DOM lib (in cycle's case snabbdom) is tailored to recognize pieces of DOM that haven't change from those which have and only updates the latter.
If it's a matter of responsibilities (those 2 pieces of DOM have very different purposes), then I would rather create two different cycle apps that both render in different parts of the DOM. (and then call run twice in your main file)
function app1(sources) {
return {
DOM: xs.of(div("hello from app1"))
}
}
function app2(sources) {
return {
DOM: xs.of(div("hello from app2"))
}
}
run(app1, {
DOM: makeDOMDriver("#app1")
})
run(app2, {
DOM: makeDOMDriver("#app2")
})
This way you have a clear separation of the concerns of both apps.
Now to answer your question about why a piece of virtual DOM needs to be wrapped in a div. It is because a piece of virtual DOM have to have a single root element. Said otherwise: a piece of virtual DOM have to be standalone (just like an HTML document only has a single <html> element which is the root).
It is, actually, a nice constraint to have because it forces you to have standalone components. In the example you give (with an <input> field), there is absolutely no problem in returning a vDOM like this:
DOM: xs.of(input(/*...*/))
But if your component has an input and a label, then you will need to wrap it in another vNode
DOM: xs.of(div([label(/*...*/), input(/*...*/)])