Browser not Defined error in Jest-Image-Snapshot Testing in React - reactjs

I want to implement https://github.com/americanexpress/jest-image-snapshot#-api in my React application and tried writing one of the test cases but gives me an error saying the browser is not defined when trying to run a test using the command "npm test".I am using a library called Jest-Image-snapshot by americanexpress
import { toMatchImageSnapshot } from 'jest-image-snapshot';
expect.extend({ toMatchImageSnapshot });
jest.setTimeout(10000);
it('renders correctly', async () => {
const page = await browser.newPage();
await page.goto('https://localhost:3000');
const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
});

Related

Why can't react testing library access elements with Selenium?

I have a selenium / jest project. I have installed react testing library and a few packages, but when I try to make valid assertions, I get an error. Code:
import { Vocal } from '../../Models/vocalModel';
import {screen} from '#testing-library/dom';
const vocal = new Vocal()
// Test Variables here
let validEmail = 'email#email.com'
let validPassword = 'password'
let invalidEmail = 'ThisEmailIsNotValid'
let invalidPassword = 'ThisPassWordIsNotValid'
describe("Login Test Suite", () => {
beforeAll(async ()=>{
await vocal.navigate();
});
afterAll( async ()=>{
await vocal.quit();
});
test('A user can login and logout', async () =>{
await vocal.userLogin(validEmail, validPassword);
await vocal.userLogout();
let getSignUpButtonText = await vocal.getText(vocal.signUpButtonLogin)
console.log(getSignUpButtonText)
expect(screen.getByText("Sign Up")).toBeVisible();
})
});
When I run this test, the browser runs and the test fails at the final assertion. I get the following error: "TestingLibraryElementError: Unable to find an element with the text: Sign Up. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.".
See photo:
Image of Error here
I've consoled 'getSignUpButtonText' which is defined as the text of that button on the page and it returns "Sign Up" so I know that text is on the page. Why can't screen.getByText('Sign Up') find it?
I have tried a few different packages but I cant seem to get away from this error.

How should I be using playwright's toHaveScreenshot() within a cucumber test in a React Typescript project?

I'm wanting to implement visual regression testing into a ReactJS app. I already have playwright setup called through cucumber for some other BDD UI tests and wanted to make use of the built in toHaveScreenShot method for visual regression. However, whenever I run the test it throws this error:
Error: toHaveScreenshot() must be called during the test
Here's the test script definition:
package.json excerpt
"test:e2e": "cucumber-js --require cucumber.conf.js --require features/step_definitions/**/*.js --format #cucumber/pretty-formatter",
Here's an example of the code:
cucumber.conf.js
const {
Before,
BeforeAll,
AfterAll,
After,
setDefaultTimeout,
} = require("#cucumber/cucumber");
const { chromium } = require("playwright");
// in milliseconds
setDefaultTimeout(60000);
// launch the browser
BeforeAll(async function () {
global.browser = await chromium.launch({
headless: false,
slowMo: 1000,
});
});
// close the browser
AfterAll(async function () {
await global.browser.close();
});
// Create a new browser context and page per scenario
Before(async function () {
global.context = await global.browser.newContext();
global.page = await global.context.newPage();
});
// Cleanup after each scenario
After(async function () {
await global.page.close();
await global.context.close();
});
homepage.feature
Feature: Homepage
A simple homepage
Scenario: Someone visiting the homepage
Given a new visitor to the site
When they load the homepage
Then they see the page
homepage.js
const { Given, When, Then } = require("#cucumber/cucumber");
const { expect } = require("#playwright/test");
Given("a new visitor to the site", function () {});
When("they load the homepage", async () => {
await page.goto("http://localhost:3000/");
});
Then("they see the page", async () => {
const locator = page.locator('img[alt="An image you expect to see"]');
await expect(locator).toBeVisible();
await expect(locator).toHaveScreenshot();
});
I think the error is complaining that I'm not writing my tests in the usual test() method, but I've not come across anything similar in searches and don't know how to give this context, assuming that is the problem, while using Cucumber.
Can anyone suggest a solution? I'm at a bit of a loss.

How to check if alert has been called in Jest testing React using Typescript?

I have a HTML select with some options which when I press a button get submitted to a server. Depending on the response an alert is shown.
The method looks like this:
const sendOrder = async (json : string) => {
const request = await fetchUrl("<irrelevant url>", "POST", json);
if(request.status === 200){
const order = await request.json();
alert(`Created Order #${order.data.id}`)
}else{
alert("Order failed")
}
}
Now I have mocked the server using msw and my test looks something like this:
it('should create an order', async () => {
render(<ArticleList />);
render(<ShoppingCart afterSubmit={()=> {}}/>)
const articlesList = getNullSafeElement('articles');
await waitFor(() => {
expect(articlesList.children.length).toBe(2);
})
const orderButton = getNullSafeElement('submitBtn');
const shoppingCart = document.querySelector('#cart') as HTMLSelectElement;
const addressInput = document.querySelector('#address') as HTMLInputElement;
addressInput.value = "testAddress"
expect(shoppingCart.options.length).toBe(0);
fireEvent.click(articlesList.children[0]);
expect(shoppingCart.children.length).toBe(1);
fireEvent.click(orderButton);
});
I know for sure that this test runs as expected and that the request is definetly beeing sent to the mocked server by fireEvent.click(orderButton).
However, if I now want to do something like this expect(global.alert).toHaveBeenCalledTimes(1), it does not work.
Now I know that I have to mock the call and I saw some posts on SO about this issue
Mock or asser whether window.alert has fired
Test alert in React Native
But neither global.alert = jest.fn(), nor jest.spyOn(global, 'alert') have worked out. Both result in 0 calls.
So my question is: How can I test if alert() has been called in my test?
(I am creating a frontend using create-react-app with the typescript template and testing with Jest)

Not Supported Error when testing suspense

I'm getting a strange error when trying to use react-testing-library to test React.Suspense. The error just says "Not Supported" but doesn't give any real insight into the problem. I followed the example that Kent Dodds did on youtube.
I posted the full code of my problem on github here, but here's a snapshot of the test code:
import React from "react";
import { render, waitForElement, cleanup } from "react-testing-library";
import MyOtherPackageThing from "my-package/lib/my-thing";
import LazyThing from "../src/index";
afterEach(cleanup);
test("it works", async () => {
const { getByText, debug } = render(<MyOtherPackageThing />);
await waitForElement(() => getByText("my thing"));
expect(getByText("my thing"));
});
describe("these fail with 'Not Supported'", () => {
test("it lazy loads a local component", async () => {
const LazyLocalThing = React.lazy(() => import("../src/LocalThing"));
const { getByText, debug } = render(
<React.Suspense fallback="Loading...">
<LazyLocalThing />
</React.Suspense>
);
debug();
await waitForElement(() => getByText("my local thing"));
debug();
expect(getByText("my local thing"));
});
test("it says not supported, like wtf", async () => {
const { getByText, debug } = render(<LazyThing />);
debug();
await waitForElement(() => getByText("my thing"));
debug();
expect(getByText("my thing"));
});
});
I encountered the same error recently. I noticed that Kent's working sample was using create-react-app and wondered if it was Babeling something special for Node/Jest. As a result of using CRA, his package.json uses the babel preset react-app.
Try installing and configuring the plugin babel-plugin-dynamic-import-node (which is the specific part of the react-app preset I think we need). I believe this plugin transforms dynamic imports into require statements for Node. More info: https://www.npmjs.com/package/babel-plugin-dynamic-import-node
install the plugin:
npm i --save-dev babel-plugin-dynamic-import-node
in my-consumer-pkg/babel.config.js, add the plugin:
plugins: [
...
"babel-plugin-dynamic-import-node"
]
...this should be enough to get past the Not Supported error.
As an aside, I noticed that one of your tests named "it lazy loads a local component" was subsequently failing with this error:
Element type is invalid. Received a promise that resolves to: [object Object]. Lazy element type must resolve to a class or function.
...so I made a minor change so that the LocalThing was a function
const LocalThing = () => <div>my local thing</div>;

Why does react hook throw the act error when used with fetch api?

I keep getting Warning: An update to App inside a test was not wrapped in act(...). in my test suite whenever I make an API request and update the state.
I'm making use of react-testing-library. I also tried using ReactDOM test utils, got the same result. One other thing I tried was wrapping the container in act, still got the same result.
Please note that: My App works and my test passes. I just need to know what I was doing wrong or if it's a bug in the react-dom package that's making that error show up. And it's bad to mock the console error and mute it.
global.fetch = require('jest-fetch-mock');
it('should clear select content item', async () => {
fetch.mockResponseOnce(JSON.stringify({ results: data }));
const { container } = render(<App />);
const content = container.querySelector('.content');
await wait();
expect(content.querySelectorAll('.content--item').length).toBe(2);
});
Here's the hook implementation:
const [data, setData] = useState([]);
const [error, setError] = useState('');
const fetchInitData = async () => {
try {
const res = await fetch(API_URL);
const data = await res.json();
if (data.fault) {
setError('Rate limit Exceeded');
} else {
setData(data.results);
}
} catch(e) {
setError(e.message);
}
};
useEffect(() => {
fetchInitData();
}, [isEqual(data)]);
It's a known problem, check this issue in Github https://github.com/kentcdodds/react-testing-library/issues/281
For anyone who stumbles upon this more than a year later as I did, the issue Giorgio mentions has since been resolved, and wait has since been replaced with waitFor, as documented here:
https://testing-library.com/docs/dom-testing-library/api-async/
That being the case, I believe the solution to the warning now should be something like this:
import { render, waitFor } from '#testing-library/react';
// ...
it('should clear select content item', async () => {
fetch.mockResponseOnce(JSON.stringify({ results: data }));
const { container } = render(<App />);
const content = container.querySelector('.content');
await waitFor(() =>
expect(content.querySelectorAll('.content--item').length).toBe(2);
);
});
In my case, I had an App component loading data asynchronously in a useEffect hook, and so I was getting this warning on every single test, using beforeEach to render App. This was the specific solution for my case:
beforeEach(async () => {
await waitFor(() => render(<App />));
});
To get rid of the act() warning you need to make sure your promises resolve synchronously. You can read here how to do this.
Summary:
The solution for this is a bit involved:
we polyfill Promise globally with an implementation that can resolve
promises 'immediately', such as promise
transpile your javascript with a custom babel setup like the one in this repo
use jest.runAllTimers(); this will also now flush the promise task queue
I had this problem and gave up using wait and async instead used jest faketimers and so on, so your code should be something like this.
global.fetch = require('jest-fetch-mock');
it('should clear select content item', /*async */ () => {
jest.useFakeTimers();
fetch.mockResponseOnce(JSON.stringify({ results: data }));
const { container } = render(<App />);
const content = container.querySelector('.content');
// await wait();
act(() => {
jest.runAllTimers();
});
expect(content.querySelectorAll('.content--item').length).toBe(2);
});

Resources