web component test mocha/chai/playwright (web test runner from modern-web): Tests were interrupted because the page was reloaded - testing

I am evaluating the web-test-runner with playwright from modern-web (https://modern-web.dev/docs/test-runner/overview/) to test my web-components.
My sample project is the following:
./package.json
{
"scripts": {
"test": "web-test-runner \"**/*.test.html\" \"**/*.test.js\" --node-resolve --playwright --browsers chromium firefox webkit --coverage",
"test:watch": "web-test-runner \"**/*.test.html\" \"**/*.test.js\" --node-resolve --playwright --browsers chromium firefox webkit --watch"
},
"devDependencies": {
"#esm-bundle/chai": "^4.1.5",
"#web/test-runner": "^0.10.0",
"#web/test-runner-playwright": "^0.6.6"
}
}
./my-component/my-component.js
(async() => {
const res = await fetch('/my-component/my-component.html');
const template = document.createElement('template');
template.innerHTML = await res.text();
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
window.customElements.define('my-component', MyComponent);
})();
./my-component/my-component.html
<style>
h2 {
color: red;
}
</style>
<h2>Hello world!</h2>
./my-component/test/my-component.test.html
<html>
<head>
<script src="/my-component/my-component.js"></script>
</head>
<body>
<script type="module">
import { runTests } from '#web/test-runner-mocha';
import { expect } from '#esm-bundle/chai';
let element;
runTests(async () => {
describe('HTML tests', () => {
beforeEach(() => {
element = document.createElement("my-component");
document.body.appendChild(element);
});
afterEach(() => {
document.body.removeChild(element);
});
it('component is loaded', () => {
expect(element.shadowRoot).to.exist;
});
it('component contains h2 with text', () => {
expect(element.shadowRoot.querySelector('h2').innerHTML).to.equal('Hello world!');
});
it('component is displayed in red color', () => {
const el = element.shadowRoot.querySelector('h2');
expect(getComputedStyle(el)).to.have.property('color', 'rgb(255, 0, 0)');
});
});
});
</script>
</body>
</html>
this test is passing successfully.
according to modern-web documentation, it is also possible to create tests within js files. So, I am trying with this test:
./my-component/test/my-component.test.js
import { runTests } from '#web/test-runner-mocha';
import { expect } from '#esm-bundle/chai';
let element;
runTests(async () => {
describe('HTML tests', () => {
before(() => {
const script = document.createElement("script");
script.setAttribute("src", "/my-component/my-component.js");
document.head.appendChild(script);
});
beforeEach(() => {
element = document.createElement("my-component");
document.body.appendChild(element);
});
afterEach(() => {
document.body.removeChild(element);
});
it('component is loaded', () => {
expect(element.shadowRoot).to.exist;
});
it('component contains h2 with text', () => {
expect(element.shadowRoot.querySelector('h2').innerHTML).to.equal('Hello world!');
});
it('component is displayed in red color', () => {
const el = element.shadowRoot.querySelector('h2');
expect(getComputedStyle(el)).to.have.property('color', 'rgb(255, 0, 0)');
});
});
});
This is basically the same as for the html test with the addition of the before directive to register my component script.
When running the js test, I get this error:
my-component/test/my-component.test.js:
❌ Tests were interrupted because the page was reloaded. This can happen when clicking a link, submitting a form or interacting with window.location.
Chromium: |██████████████████████████████| 1/1 test files | 0 passed, 0 failed
Firefox: |██████████████████████████████| 1/1 test files | 0 passed, 0 failed
Webkit: |██████████████████████████████| 1/1 test files | 0 passed, 0 failed
View full coverage report at coverage/lcov-report/index.html
How could I solve it?
Best Regards,

Related

wrapper.find({ ref:''}) not working in vue testing utils

I have this component (using Pug framework):
v-btn.float-right(
ref='copyContentButton'
x-small
v-on='{...ontooltip}'
#click='copyContent'
)
v-icon(small color='grey darken-2') $vuetify.icons.faCopy
span Copy content
I am trying to test the Copy content button using the test below:
describe('Comment.vue', () => {
let options: ShallowMountOptions<Vue>;
let wrapper: Wrapper<Vue>;
beforeEach(() => {
options = {
localVue,
propsData: {
correspondence: TEST_CORRESPONDENCE,
},
vuetify: new Vuetify(),
};
wrapper = shallowMount(CorrespondenceComment, options);
});
describe('copy button', () => {
it('should include a copy content button', () => {
const copyButton = wrapper.find({ ref: 'copyContentButton' });
expect(copyButton.exists()).toBe(true);
});
it('should copy text and indicate success', async () => {
const copyButton = wrapper.find({ ref: 'copyContentButton' });
const copyText = jest.fn();
(wrapper.vm as any).$copyText = copyText;
const info = jest.fn();
(wrapper.vm as any).$toasted = { info };
wrapper.setProps({
correspondence: {
...TEST_CORRESPONDENCE,
content: 'my message content',
},
});
copyButton.trigger('click');
await wrapper.vm.$nextTick();
expect(copyText).toHaveBeenCalledWith('my message content');
expect(info).toHaveBeenCalledWith('Content copied to clipboard.');
});
});
The tests are failing because it is not able to find the component by ref, even though this is the right syntax.
Any idea what I may be missing?

Timeout simulation not working with testing-library and useFakeTimers

I'm working on a vueJS component that allows to display a modal after 5 seconds. the component works well as expected.
<template>
<vue-modal v-if="showModal" data-testid="modal-testid" />
</template>
<script>
export default {
name: "TimeoutExample",
data() {
return {
showModal: false,
}
},
mounted() {
setTimeout(() => this.displayModal(), 5000)
},
methods: {
displayModal: function() {
this.showModal = true;
}
}
};
</script>
I implemented the unit tests using jest, testing-library and I wanted to use jest.useFakeTimers to simulate the timeout, but the test is KO.
// testing file
describe.only('Vue Component (mobile) 2', () => {
beforeAll(() => {
isMobile.mockImplementation(() => true)
})
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runOnlyPendingTimers()
jest.useRealTimers()
})
it('should render title after `props.delay` milliseconds', () => {
const { queryByTestId } = myRender({
localVue: myMakeLocalVue(),
})
jest.advanceTimersByTime(5001)
expect(queryByTestId('modal-testid')).toBeVisible()
})
})
do you have any idea how i can test this behavior?
remove this jest.spyOn(global, 'setTimeout'). jest will do it's own magic with for this with useFakeTimers
I suppose you can not use async and done callback in one test case. Which version of jest do you use?
Add await localVue.$nextTick() after advanceTimersByTime to wait until Vue apply all the changes
It works for me after calling advanceTimersByTime inside waitFor.
describe.only('Vue Component (mobile) 2', () => {
beforeAll(() => {
isMobile.mockImplementation(() => true)
})
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runOnlyPendingTimers()
jest.useRealTimers()
})
it('should render title after `props.delay` milliseconds', async () => {
const { queryByTestId } = myRender({
localVue: myMakeLocalVue(),
})
await waitFor(() => {
jest.advanceTimersByTime(5001)
})
expect(queryByTestId('modal-testid')).toBeVisible()
})
})

How to make unit test "location.href" by jest Vue.js

What I want to do
I wanna make test that is redirect by click.
if possible, I wanna make test not only method called assertion but redirect "URL" assertion.
Test Target Code
<p class="target" v-on:click="redirect">Click!</p>
<script>
export default {
methods: {
redirect() {
window.location.href = '/home'
},
}
}
Test Code
import { shallowMount } from '#vue/test-utils'
import redirectComponent from '../components/RedirectComponent.vue'
describe('Redirect component', () => {
const wrapper = shallowMount(redirectComponent)
it('Redirect By Click Test', () => {
wrapper.find('.target').trigger('click');
expect(window.location.href).toEqual('/home');
})
})
result
Expected: "/home"
Received: "http://localhost/"
How can I make redirect test?
window.location.href gives you the URL of the current page;
window.location.pathname will give you the path name of the current page, which I believe, is what you are looking for.
This is my Solution!
import { shallowMount } from '#vue/test-utils'
import redirectComponent from '../components/RedirectComponent.vue'
describe('Redirect component', () => {
const wrapper = shallowMount(redirectComponent)
Object.defineProperty(window, 'location', {
value: {
href: 'http://localhost',
},
configurable: true,
});
it('Redirect By Click Test', () => {
wrapper.find('.target').trigger('click');
expect(window.location.href).toEqual('/home');
})
})

Vue Test Utils mount in multiple tests

I am testing my Vue App using Vue Test Utils and Jest. Below is my dashboard component.
<template>
<div class="dashboard-v2">
<div class="component-container">
<component :loading="loading" :key="identifier" :is="currentTab" />
</div>
<SnackBar
v-on:snackBarHide="displaySnackBar = false"
:text="snackBarText"
:show="displaySnackBar"
:type="snackBarType"
/>
</div>
</template>
<script>
import { mapState } from "vuex";
import "#/shared/chart-kick";
import EventBus from "#/shared/event-bus";
import Tabs from "./helpers/Tabs";
import Summary from "./Summary/Index";
import { filters } from "../helpers/filters-details";
import SnackBar from "#/shared/components/SnackBar.vue";
export default {
components: {
Tabs,
Summary,
SnackBar
},
data() {
return {
identifier: +new Date(),
loading: false,
filtersLoading: false,
displaySnackBar: false,
snackBarText: "",
snackBarType: ""
};
},
mounted() {
if (!this.projects.length) this.fetchFilterData();
EventBus.$on("CLEAR_ALL", () => {
this.identifier = +new Date();
this.$store.commit(`dashboardV2/UPDATE_FILTER_STATE`, {});
});
EventBus.$on("filterChange", () => {
this.getExecData();
});
},
computed: {
...mapState("dashboardV2", [
"projects",
"currentTab",
"selectedFilters",
"timeFilter"
])
},
methods: {
fetchFilterData() {
this.filtersLoading = true;
this.$store
.dispatch("dashboardV2/GET_EXEC_FILTER_DATA")
.catch(() => {
this.displaySnackBar = true;
this.snackBarText = "There was some problem while fetching data";
this.snackBarType = "failure";
})
.finally(() => {
this.filtersLoading = false;
});
this.getExecData();
},
getExecData() {
this.loading = true;
let params = {
time_bucket: this.timeFilter,
time_zone_offset: new Date().getTimezoneOffset()
};
filters.map(e => {
params[e.query] = this.selectedFilters[e.value]
? this.selectedFilters[e.value].id
: null;
});
this.$store
.dispatch("dashboardV2/GET_EXEC_DATA", params)
.catch(() => {
this.displaySnackBar = true;
this.snackBarText = "There was some problem while fetching data";
this.snackBarType = "failure";
})
.finally(() => (this.loading = false));
}
}
};
</script>
<style lang="scss" scoped>
#import "#/styles/dashboard.scss";
</style>
Then this is my test file
import Main from "../Main.vue";
import mergeWith from "lodash.mergewith";
import { customizer, createWrapper } from "#/shared/test-helper";
import Vuex from "vuex";
import EventBus from "#/shared/event-bus";
let GET_EXEC_DATA = jest.fn(() => Promise.resolve());
let GET_EXEC_FILTER_DATA = jest.fn(() => Promise.resolve());
export const createStore = (overrides) => {
let storeOptions = {
modules: {
dashboardV2: {
namespaced: true,
state: {
projects: [],
currentTab: "",
selectedFilters: {},
timeFilter: "",
},
actions: {
GET_EXEC_DATA,
GET_EXEC_FILTER_DATA,
},
},
},
};
return new Vuex.Store(mergeWith(storeOptions, overrides, customizer));
};
describe("Loads Main Dashboard", () => {
it("should fetch chart data and filter data", () => {
createWrapper({}, Main, createStore());
expect.assertions(2);
expect(GET_EXEC_DATA).toBeCalled();
expect(GET_EXEC_FILTER_DATA).toBeCalled();
});
it("should call fetch chart data when filter changed", () => {
createWrapper({}, Main, createStore());
EventBus.$emit("filterChange");
expect.assertions(1);
expect(GET_EXEC_DATA).toBeCalledTimes(2);
});
});
My first test is running successfully but my second test is failing because GET_EXEC_DATA is being called 4 times instead of 2 times. Is it because it's being called once in the first test. Then, How do I avoid this?
Actually, I was able to solve this by clearing the mock functions
afterEach(() => {
jest.clearAllMocks();
});

how to do page navigation (routing) using karma-Jasmine test

I am writing the angular (Karma-Jasmine) test cases and I want to navigate between the pages. How to navigate between pages using karma-Jasmine.
1) Test a component in which navigation is used: navigate method should be called when you do an action (assertion like toHaveBeenCalled OR toHaveBeenCalledWith)
it('should redirect the user to the users page after saving', () => {
let router = TestBed.get(Router);
let spy = spyOn(router, 'navigate');
component.save();
expect(spy).toHaveBeenCalledWith(['users'])
});
2) Also test your routes that proper component will be used
app.routes.spec.ts
import { routes } from './app.routes'
it('should contain a route for users', () => {
expect(routes).toContain({path: 'users', component: UserComponent})
});
3) You can use useValue for testing different activatedRouteParams (just configure then and pass to providers, see example).
Component ts file example:
ngOnInit() {
this.contextType = this.route.snapshot.paramMap.get('contextType');
this.contextId = this.route.snapshot.paramMap.get('contextId');
this.contextFriendlyId = this.route.snapshot.paramMap.get('contextFriendlyId');
}
Spec file (configureTestData is a function that allows you to pass different configurable values, in my case activatedRouteParams)
export function configureTestData(activatedRouteParams) {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SetUpComponent],
imports: [RouterTestingModule],
providers: [
{
provide: ActivatedRoute, useValue: activatedRouteParams
}
]
});
}));
}
describe('SetUp Component:Folder ', () => {
let component: SetUpComponent;
let fixture: ComponentFixture<SetUpComponent>;
configureTestData({
snapshot: {
paramMap: convertToParamMap({
contextType: 'Folder',
contextId: 'FX6C3F2EDE-DB25-BC3D-0F16-D984753C9D2E',
contextFriendlyId: 'FX17373'
})
}
});
beforeEach(() => {
fixture = TestBed.createComponent(SetUpComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create set up component for folder', () => {
expect(component).toBeTruthy();
});
it('should create alert with required properties', () => {
expect(component.contextType).toEqual('Folder);
.... etc
});
});
4) router-outlet and routerLink
Template file:
<nav>
<a routerLink="todos"></a>
</nav>
<router-outlet></router-outlet>
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([])],
declarations: [AppComponent]
});
});
it('should have router outlet', () => {
let de = fixture.debugElement.query(By.directive(RouterOutlet));
expect(de).not.toBeNull();
});
it('should have a link to todos page', () => {
let debugElements = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
let index = debugElements.findIndex(de => de.properties['href'] === '/todos');
expect(index).toBeGreaterThan(-1);
});
5) Stub for ActivatedRoute where we can push params
component.ts
ngOnInit() {
this.route.params.subscribe(p => {
if (p['id'] === 0) {
this.router.navigate(['not-found']);
}
});
}
Spec file:
class RouterStub {
navigate(params) {}
}
class ActivatedRouteStub {
private subject = new Subject();
get params () {
return this.subject.asObservable();
}
push(value) {
this.subject.next(value);
}
}
describe('Navigation Testing', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
providers: [
{provide: Router, useClass: RouterStub},
{provide: ActivatedRoute, useClass: ActivatedRouteStub}
]
});
});
it('should navigate to invalid page when invalid params passed', () => {
let router = TestBed.get(Router);
let spy = spyOn(router, 'navigate');
let route: ActivatedRouteStub = TestBed.get(ActivatedRoute);
route.push({id: 0});
expect(spy).toHaveBeenCalledWith(['not-found'])
});
});