I'm trying to use Stencil e2e screenshot tests which have Puppeteer v10 under the hood.
I'm facing the problem with texts and fonts. Sometimes they are hidden and sometimes they appear as they should.
I'm using the following code for my tests:
const componentStatesTemplate = `
<div style="display: flex; flex-direction: column;">
<nl-button>Button</nl-button>
<nl-button is-disabled>Button</nl-button>
<nl-button is-full-width>button</nl-button>
<nl-button><nl-icon-heart></nl-icon-heart></nl-button>
<nl-button><nl-icon-heart></nl-icon-heart>Button</nl-button>
<nl-button variant="secondary">Button</nl-button>
<nl-button variant="secondary"><nl-icon-heart></nl-icon-heart></nl-button>
<nl-button variant="secondary"><nl-icon-heart></nl-icon-heart>Button</nl-button>
</div>
`;
describe("button component", () => {
it("is rendered", async () => {
const page = await newE2EPage();
await page.setContent(html);
await page.addStyleTag({
content: `
#font-face {
font-family: "Source Sans 3";
src: url("${getAssetPath("../assets/fonts/SourceSans3-VariableFont_wght.ttf")}");
font-weight: 100 900;
}
body {
-webkit-font-smoothing: antialiased;
}
`,
});
const results = await page.compareScreenshot();
expect(results).toMatchScreenshot({allowableMismatchedPixels: E2EConstants.DEFAULT_allowableMismatchedPixels});
expect(results).toMatchScreenshot({allowableMismatchedRatio: E2EConstants.DEFAULT_allowableMismatchedRatio});
});
});
Environment:
I'm running tests in docker, using alpine linux with Chromium installed as described in Puppeteer docs.
I'm also applying --font-render-hinting=none flag as described in many articles on the subject.
Using waitForOptions.waitUntil set to networkidle0 or domcontentloaded did not help as well.
However, the text in my components is not shown stable. Sometimes tests run as supposed and sometimes the components are rendered as if text was hidden.
How do I make them stable?
Adding await page.evaluateHandle("document.fonts.ready"); helped, now fonts load properly.
Example:
const page = await newE2EPage();
await page.setContent(html);
await page.addStyleTag({
content: `
#font-face {
font-family: "Source Sans 3";
src: url("${getAssetPath("../assets/fonts/SourceSans3-VariableFont_wght.ttf")}");
font-weight: 100 900;
}
body {
-webkit-font-smoothing: antialiased;
}
`,
});
// Inserting the wait before comparing elements
await page.evaluateHandle("document.fonts.ready");
const results = await page.compareScreenshot();
Related
I am using Cypress 10 and use the interface a lot when building my components, but find the light theme harsh on my eyes, is there a dark mode available?
TLDR
Copy and paste the below code in cypress/support/component.ts.
//cypress/support/component.ts
import { mount } from 'cypress/react'
import './commands'
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add('mount', (jsx, options) => {
assertDarkThemeAttached()
const result = mount(jsx, options)
result.then(() => parent.window.document.querySelector('iframe')!.style.display = '')
return result
})
const assertDarkThemeAttached = () => {
const parentHead = Cypress.$(parent.window.document.head)
if (parentHead.find('#cypress-dark').length > 0)
return
parentHead.append(`<style type="text/css" id="cypress-dark">\n${style}</style>`)
parent.window.eval(`
const observer = new MutationObserver(() => {
const iframe = document.querySelector('iframe')
if(!iframe) return;
const cyRoot = iframe.contentDocument.body.querySelector('[data-cy-root]')
if(!cyRoot) iframe.style.display = 'none'
// iframe.style.display = cyRoot ? '' : 'none'
})
observer.observe(document.body, { attributes: true, childList: true, subtree: true })
`)
}
//wrapper for syntax highlighting
const css = (val: any) => val
const style = css`
#media (prefers-color-scheme: dark) {
:root {
--active-color: #cccccc;
--background: #222426;
}
html, body, #spec-filter, .h-full, .flex, .spec-container, .bg-white{
background-color: var(--background) !important;
color: var(--active-color) !important;
}
}
`
How it works
Cypress.Commands.add(...
Override the mount function to make sure the dark theme is attached and then mount. Note it will only 'go dark' once the first test is mounted.
#media (prefers-color-scheme: dark)...
Match your os theme, Comment out the block to have it on all the time.
parent.window.eval(...
Disable the iframe while the test is loading or we'll get a horrible white flash. It is enabled again on mount.
Notes:
I've only tested this on the Chrome Component test runner
this doesn't effect the tests themselves, thats up to you to make dark mode for your site :)
Extra Credit - Dark Chrome Theme
Before installing a new theme the cypress one must be deleted, otherwise it will revert any changes made the next time the browser is opened.
Delete the theme folder from the cache, ie C:\Users\USERNAME\AppData\Local\Cypress\Cache\CYPRESS_VERSION\Cypress\resources\app\packages\extension
Open Cypress Chrome
Install a dark theme, ie just black
I am using replit to host a chat server using Node.js and Socket.io. I used the getting started guide provided by socket.io (https://socket.io/get-started/chat), and saw that the HTML code used internal CSS. I attempted to change this and have the following code:
index.js
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
io.on('connection', (socket) => {
socket.on('chat message', (msg) => {
io.emit('chat message', msg);
});
});
server.listen(3000, () => {
console.log('Site is up and running!');
});
index.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var messages = document.getElementById('messages');
var form = document.getElementById('form');
var input = document.getElementById('input');
form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
socket.emit('chat message', input.value);
input.value = '';
}
});
socket.on('chat message', function(msg) {
var item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
</body>
</html>
styles.css
body {
margin : 0;
padding-bottom : 3rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
#form {
background : rgb(0, 0, 0, 0.15);
padding : 0.25rem;
position : fixed;
bottom : 0;
left : 0;
right : 0;
display : flex;
height : 3rem;
box-sizing : border-box;
backdrop-filter : blur(10px);
}
#input {
border : none;
padding : 0 1rem;
flex-grow : 1;
border-radius : 2rem;
margin : 0.25rem;
}
#input:focus {
outline : none;
}
#form > button {
background : #333;
border : none;
padding : 0 1rem;
margin : 0.25rem;
border-radius : 3px;
outline : none;
color : #fff;
}
#messages {
list-style-type : none;
margin : 0;
padding : 0;
}
#messages > li {
padding : 0.5rem 1rem;
}
#messages > li:nth-child(odd) {
background : #efefef;
}
However, when I run the repl the expected result does not show up and the CSS does not apply to the HTML. Could anyone tell me what is going on?
I think you should use the express.static() method in the index.js file. This method allows us to serve static files. releated documentation
To serve static files such as images, CSS files, and JavaScript files, use the express.static built-in middleware function in Express.
The function signature is:
express.static(root, [options])
The root argument specifies the root directory from which to serve static assets.
For example, use the following code to serve images, CSS files, and
JavaScript files in a directory named public:
app.use(express.static('public'))
Now, you can load the files that are
in the public directory:
http://localhost:3000/images/kitten.jpg
http://localhost:3000/css/style.css http://localhost:3000/js/app.js
http://localhost:3000/images/bg.png http://localhost:3000/hello.html
Express looks up the files relative to the static directory, so the
name of the static directory is not part of the URL. To use multiple
static assets directories, call the express.static middleware function
multiple times:
app.use(express.static('public'))`
app.use(express.static('files'))
Express looks up the files in the order in which you set the static
directories with the express.static middleware function.
I'm using puppeteer to convert some HTML to PNG using the screenshot method.
First, I fetch some SVG, then I create a page, and set the SVG as page content.
fetch(url)
.then(data => data.text())
.then((svgText) => {
// res.set('Content-Type', 'text/html');
const $ = cheerio.load(svgText)
return $.html()
})
.then(async (html) => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setContent(html)
const file = await page.screenshot()
res.set('Content-Type', 'image/png');
await browser.close()
res.send(file)
})
.catch((err) => {
console.log(err);
logger.log({
level: 'error', message: 'GET /product', err
})
})
})
The problem is, texts in my SVG includes a specific font. This font is loaded using the #import CSS tag. If I set my method to return the HTML, the fonts are loaded, then, after a slight delay, they get applied to my texts. Unfortunately, when using the screenshot method, my texts are not styled anymore. I suppose it is because the screenshot is taken before the fonts are loaded and applied, therefore rendering a text with a fallback font.
Is there a way to make sure that the page is completely rendered before taking the screenshot ?
I tried using the page.on('load') event listener, but this doesn't change anything the script just runs forever.
Here is what i am using as of now.
gulp.task('test', function () {
return gulp.src([ './app/spec/*.js'])
.pipe(jasmine());
});
But in case of grunt, you can mention source files like this
jasmine: {
src: 'js/**/*.js',
options: {
specs: 'spec/**/*.js'
}
}
Where can i mention the location for source files in case of gulp ?
Another issues is that gulp only runs the test cases from first file, as of now all of them fails, and it doesn't go to the second files in the spec folder.
For issue 2 I see this gulp-jasmine option:
errorOnFail
Default: true
Stops the stream on failed tests.
.pipe(jasmine({errorOnFail: false}))
should run the rest of the tests.
Issue 1: There are a number of ways to indicate multiple sources for gulp.src such as
return gulp.src([ './app/spec/*.js', 'spec/**/*.js'])
or set up a variable:
var testSources = ['./app/spec/*.js', 'spec/**/*.js'];
return gulp.src(testSources)
or I use something like this:
var paths = {
html: {
src: "home.html",
temp: "./temp",
deploy: "./deploy/html"
},
sass: {
src: "./src/styles/scss/**/*.scss",
stylesFile: "./src/styles/scss/styles.scss"
},
css: {
src: "./temp/css/styles.css",
temp: "./temp/css",
deploy: "./deploy/css"
},
js: {
src: "./src/js/**/*.js",
temp: "./temp/js",
deploy: "./deploy/js"
}
};
and then to use:
function reloadJS() {
return gulp.src(paths.js.src)
.pipe(sourcemaps.init())
.pipe(sourcemaps.write("../sourcemaps", {includeContent:false,sourceRoot:"/js"}))
.pipe(gulp.dest(paths.js.temp))
.pipe(reload({ stream:true }));
}
BTW, putting two questions into one post is generally frowned upon on SO.
I am learning ReactJS by creating ReactJS version of my blog. While I was testing google page speed, I am noticed the "prioritize visible content", fine, while articles are loading I have added a placeholder article with title of a loading message and lorem ipsum description and a sample default header image. I have run the page speed again, still the same issue with one difference. The placeholder article is rendered, but the title, image and description are not rendered. It is a static text, cannot imagine why it´s not showing. I have tried to simulate low internet connection and reload the page and yes, the text inside component is rendered with some delay, even its just static text.
The code is available here: https://github.com/erikkubica/reactjs-simple-blog-test See the src/modules/article/ArticleListItemPlaceholder.js and ArticleList.js
I have also noticed that the logo is also missing at this moment. Cannot imagine why, if on non-react website it´s not. Also the styles are loaded, navigation component is rendered...
See in action http://reactjs.netlime.eu/
Screenshot about the problem:
Thank you, I will be happy to get any explanation, good practices,... to learn more.
UPDATE:
Problem resolved.
Problem was that while custom fonts was not loaded browser made the text invisible. I have added fontFamily: "Arial" into inline style of elements which fixed the issue. Thanks to Mr Lister.
Also big thanks to John Ruddell for giving some best practice.
Your issue is you are setting state async but trying to render things in a semi sync manner.
async fetchData() {
//const response = await fetch("/static/articles.json");
let data = null; //await AsyncStorage.getItem('#MySuperStore:articles_storage_' + this.state.category);
if (data === null) {
const response = await fetch("https://www.netlime.eu/wp-json/get-posts/v2/all/");
data = await response.json();
await AsyncStorage.setItem('#MySuperStore:articles_storage_' + this.state.category, JSON.stringify(data));
}
try {
data = JSON.parse(data);
} catch (e) {
}
// your issue is here
______________________________________________
this.setState({articles: data});
this.createArticleList();
______________________________________________
// change to this.
this.setState({articles: data}, () => {
this.createArticleList();
});
}
because setState is async you are creating the articleList state item before the articles are set on state. so the UI never gets updated
Edit
To be honest you shouldn't use a secondary state variable to hold an array of the articles to be rendered. create them on the fly.
export default class ArticleList extends React.Component {
constructor(props) {
super(props);
this.state = {
articles: [],
category: this.props.category ? this.props.category : 0
};
}
async fetchData() {
let data = null; //await AsyncStorage.getItem('#MySuperStore:articles_storage_' + this.state.category);
if (data === null) {
const response = await fetch("https://www.netlime.eu/wp-json/get-posts/v2/all/");
data = await response.json();
await AsyncStorage.setItem('#MySuperStore:articles_storage_' + this.state.category, JSON.stringify(data));
}
try {
data = JSON.parse(data);
} catch (e) {
}
this.setState({articles: data});
}
componentDidMount() {
this.fetchData();
}
render() {
const articles = this.state.articles.map((article) => {
return (
<ArticleListItem
key={article.ID}
article={article}
/>
);
});
return (
<div>
{ articles.length ? articles : <ArticleListItemPlaceholder/>}
</div>
);
}
}