Saving local storage is being flaky - Cypress - javascript

I saw some other posts about local storage but they all relate to tokens and login.
We have an iframe that gets created and pops in from the right on our site upon first visit, I'm trying to keep this iframe from ever opening. A dev put an identifier in place for me to tell it's my Cypress test and to not fire the iframe but it's flaky.
I am using the plugin https://www.npmjs.com/package/cypress-localstorage-commands to handle my local storage.
This is in my Command file:
import "cypress-localstorage-commands";
In my test, I have the following:
beforeEach(() => {
cy.restoreLocalStorage();
cy.setLocalStorage('is_cypress_test', 'true');
})
afterEach(() => {
cy.saveLocalStorage();
})
However, this frequently fails and the iframe opens. When it works, it also prints out to console that Cypress was detected (this is something added on our sites code to verify it was working).
Here is the basic look of my test.
/// <reference types="Cypress" />
describe(`it browses to xxxx`, () => {
// sets up service cookie to preserve session
Cypress.Cookies.defaults({
preserve: 'foo',
});
beforeEach(() => {
cy.setLocalStorage('is_cypress_test', 'true');
cy.restoreLocalStorage();
})
afterEach(() => {
cy.saveLocalStorage();
})
it(`should log in via a POST, and browse xxx`, () => {
cy.serviceLoginByCSRF(Cypress.env('user_name'), Cypress.env('password'));
cy.visit('/#/asitepage');
});
describe(`it checks all xxxxx`, () => {
it(`should verify xxxxxx`, () => {
cy.get('h3').should('be.visible').invoke('text').then(data => {
let regex = /\n|\*|Back/g;
cy.textCleanup(data, regex).should('eq', 'bar');
});
});
});
describe(`it checks all yyyy`, () => {
it(`should verify yyyy`, () => {
cy.get('h3').should('be.visible').invoke('text').then(data => {
let regex = /\n|\*|Back/g;
cy.textCleanup(data, regex).should('eq', 'foo');
});
});
});
});
Beamer code
<!-- Beamer for product updates -->
<script>
var beamer_config = {
product_id: "foobar",
selector: "#beamer",
user_email: 'example#test.blogspot.gov',
user_firstname: 'Hank',
user_lastname: 'Williams',
filter: 'production',
onopen: function(){
// localStorage.setItem("is_cypress_test", "true") -- is test
if(localStorage.getItem("is_cypress_test")){
console.log("Skipping beamer load for Cypress");
return false;
}
}
};
</script>
<script type="text/javascript" src="https://asite.js" defer="defer"></script>
<!-- // Beamer for product updates -->
I'm wondering if I'm setting this in the wrong way, or wrong area?
Any help, or notes on how best to use this so it will always have that in localStorage before every test would be greatly appreciated.
Thoughts?
Thanks

Had the same issue, what fixed it for me was adding the following code to commands.js:
Cypress.LocalStorage.clear = function (keys, ls, rs) {
return;
}

Related

Method to not catch element in cypress test everytime its needed

I am writing tests for Dark Mode Actions in cypress and I am operating mostly on header. Because of it I am catching it very often using cy.get("header). I am wondering if there is any way to save it in any variable so there is no need to catch it every time and use something like header.contains for example. Documentation of cypress says that simple const header = cy.get("header") doesn't work. Do you know any method to solve this problem so my code will be a little bit cleaner?
Part of test code
it("toggles darkmode", () => {
//when
cy.visit("localhost:3000");
cy.get("header").contains("title", "moon-icon").click({ force: true });
cy.get("header").should("contain", "sun-icon");
cy.get("header").contains("title", "sun-icon").click({ force: true });
cy.get("header").should("contain", "moon-icon");
});
it("remebers dark mode after refresh", () => {
//when
cy.visit("localhost:3000");
cy.get("header").contains("title", "moon-icon").click({ force: true });
cy.reload();
//then
cy.get("header").should("contain", "sun-icon");
});
Assuming all of your tests in this same describe block have the same setup, you could alias the cy.get('header') in a beforeEach.
describe('test', () => {
beforeEach(() => {
cy.visit('localhost:3000');
cy.get('header').as('header');
});
it("toggles darkmode", () => {
//when
cy.get("#header").contains("title", "moon-icon").click({ force: true });
cy.get("#header").should("contain", "sun-icon");
cy.get("#header").contains("title", "sun-icon").click({ force: true });
cy.get("#header").should("contain", "moon-icon");
});
});
Take a look at .within() to set scope of commands.
cy.get("header").within($header => {
cy.contains("title", "moon-icon")
.click()
.should("contain", "sun-icon")
cy.contains("title", "sun-icon")
.click()
.should("contain", "moon-icon")
})

Cypress V10. Not Reading data in BDD test: TypeError: Cannot read properties of undefined (reading 'mobileHandset')

I hope someone can help, I've just converted a Cypress Mocha framework to BDD. Before converting it was running perfectly and the test was running smoothly. Now I've converted it I seem to be getting an error message Cannot read properties of undefined (reading 'mobileHandset'). I never had this issue before so I'm very confused. here is the code and watch this video
Feature:
Feature: End to End Shopping Purchase Validation
Registered user will be able to purchase an item and have it shipped to their country
Scenario: Customer Purchase and delivery
Given I am on the eCommerce page
When I add items to cart
And I confirm shopping cart total
Then I select my delivery country and see a thank for your order notification
Step Definition
import { Given, And, Then, When } from "#badeball/cypress-cucumber-preprocessor";
import Homepage from '../../../support/pageObjects/Homepage'
import orderSummaryPage from '../../../support/pageObjects/orderSummaryPage'
import completeOrderPage from '../../../support/pageObjects/completeOrderPage'
const data = require ('../../../fixtures/example.json');
const homepage = new Homepage()
const StartCheckout = new orderSummaryPage()
const CompleteOrder = new completeOrderPage()
Given(/^I am on the eCommerce page$/, () => {
cy.visit(``+"/angularpractice/")
});
When(/^I add items to cart$/, function() {
homepage.getShopTab().click({force:true})
this.data.mobileHandset.forEach(function(element) {// this custom commad will add items to your cart
cy.AddToCart(element)
});
StartCheckout.getBasketCheckoutButton().click()
});
When(/^I confirm shopping cart total$/, () => {
let sum=0
CompleteOrder.getProductCost().each(($e1, index, $list) =>{
const unitCost=$e1.text()
let res= unitCost.split(" ")
res= res[1].trim()
sum=Number(sum)+Number(res)
}).then(function()
{
cy.log(sum)
})
});
Then(/^I select my delivery country and see a thank for your order notification$/, () => {
StartCheckout.getStartCheckoutButton().click()
CompleteOrder.getShippingCountry().type('United Kingdom')
CompleteOrder.getShippingCountryConfirm().click()
CompleteOrder.getTermsConditionsCheckbox().click({force: true})
CompleteOrder.getPurchaseButton().click()
CompleteOrder.getPurchaseAlert().then(function(element){
const actualText= element.text()
expect(actualText.includes('Success')).to.be.true
})
});
Here is the data
{
"name": "MY_NAME",
"gender": "Female",
"mobileHandset": ["Blackberry", "Nokia Edge"]
}
BeforeEach
beforeEach(function()
{
cy.fixture('example').then(function(data){
this.data=data
})
})
After discussion I moved the BeforeEach file to Support. Still getting the original error
You don't need to import the data fixture if you already have it in the cypress/fixtures folder.
You can load the fixture in the Before hook before your tests.
import {
Given,
And,
Then,
When,
Before
} from "#badeball/cypress-cucumber-preprocessor";
//...
Before(function() {
cy.fixture('example').then((data) => {
this.data = data;
});
});
//...
Your beforeEach() should be working, but it's not necessary you can just refer to data instead of this.data.
const data = require ('../../../fixtures/example.json'); // data available anywhere in this step
...
When(/^I add items to cart$/, () => {
...
data.mobileHandset.forEach(element => {
cy.AddToCart(element)
})
...
})
The convention is to use cy.fixture()
When(/^I add items to cart$/, () => {
...
cy.fixture('example.json').then(data => { // no ../.. needed
data.mobileHandset.forEach(element => {
cy.AddToCart(element)
})
})
...
});

How to reset Cypress window.location.href after test

I am testing some ui that on click updates the window.location.href. I have two tests, the first one works, but the second starts in the location set by the ui in the previous test. This is wrong and stops test two from starting on the right page.
How can I reset the window.location.href or just the Cypress browser location in general back to where it was at the beginning of the first test?
I have checked the window.location.href at the start of the second test and it looks autogenerated and so I don't think wise to try and hardcode that value into window.location.href at the start of the second test.
Looking for something I can run at afterEach.
Test
it.only('should send asynchronous analytics event after provider selection click', () => {
rewire$useFlag(() => true);
cy.location().then((location) => console.log('window !!'));
const analyticsAsyncStub = cy.stub().as('sendAnalyticsAsyncStub');
rewire$sendAnalyticsAsync(analyticsAsyncStub);
// #NOTE hacking browser detection so required provider options are availiable
cy.window().then(($window) => {
console.log('window !!', $window.location.href);
($window as any).chrome = {};
($window as any).chrome.runtime = {
sendMessage() {
'mock function';
},
};
});
mountFixtureWithProviders({ children: <ProviderSelection flagsConfig={defaultFlags} /> });
cyGetByTestId('provider--metaMask').click();
cy.get('#sendAnalyticsAsyncStub').should(
'have.been.calledWithMatch',
analyticsUtilsModule.createButtonEvent(ButtonEventName.providerSelectionPressed),
);
});
mountFixtureWithProviders function
export const mountFixtureWithProviders = ({
children,
mountInsideVisualMock = true,
setErrorLog = () => ({}),
renderErrorScreens,
}: {
children: ReactNode;
mountInsideVisualMock?: boolean;
setErrorLog?: SetErrorLog;
renderErrorScreens?: boolean;
}) => {
const RouterMockedChildren = () => <MemoryRouter>{children}.
</MemoryRouter>;
const ProvidedChildren = () =>
renderErrorScreens ? (
<DemoAppToRenderErrorMessages>
<RouterMockedChildren />
</DemoAppToRenderErrorMessages>
) : (
<LinkUiCoreContext.Provider value={{ setErrorLog,
imageResizerServiceUrl: DEV_IMAGE_RESIZER_SERVICE_URL }}>
<RouterMockedChildren />
</LinkUiCoreContext.Provider>
);
return mount(mountInsideVisualMock ? linkPageVisualMock({
children: <ProvidedChildren /> }) : <ProvidedChildren />);
};
Thank you.
Usually you can just do a visit in a beforeEach() to get a clean start for each test
beforeEach(() => {
cy.visit('/')
})
There's also cy.go('back') which you can run at the end of the first test.
But be aware that a fail in test 1 will then fail test 2 because the navigation won't happen - same applies to adding into afterEach().
In Cypress window is the test runner window, but you can access the app window with
cy.window().then(win => console.log(win.location.href))
or the location directly with
cy.location().then(loc => console.log(loc.href))
Don't use cy.log() for debugging, use console.log() as there are side-effects to cy.log() that may give you the wrong debugging info.
(mountFixtureWithProviders({ children: <ProviderSelection flagsConfig={{}} /> }) looks like a component test, so yes I agree that should give you a fresh start for each test.
Can you add the two tests and also mountFixtureWithProviders in the question to give the full picture please.

How to chrome.runtime.reload() a Chrome Extension when building it with Webpack 5 Boilerplate?

I am using https://github.com/lxieyang/chrome-extension-boilerplate-react as the basis to build a chrome extension. It all works fine, and everything does hot-reloading (popup, background, options, newtab) except for the content-script. Reloading the matching pages, does not reload the underlying .js. It takes to reload/turn-off-on the whole extension in order for the changes to go into effect.
So, in webpack.config.js i commented out 'contentScript' hoping for it to fix that, but it makes no difference.
...
chromeExtensionBoilerplate: {
notHotReload: [
//'contentScript'
],
},
...
In src/pages/Content/index.js it actually states
console.log('Must reload extension for modifications to take effect.');
When developing another extension in plain vanilla js, i dropped a hot-reload.js from https://github.com/xpl/crx-hotreload which worked perfectly. From what i understand it is the 'chrome.runtime.reload()' call that makes chrome completely reload the extension.
So my question(s) actually is:
When changing src/pages/Content/index.js, webpack does re-build the build/contentScript.bundle.js. But why doesn't manually reloading the tab/page recognize these changes, when for popup, background, etc. it does?
And if there is no way to let the above boilerplate reload the extension (i don't mind the hard reload) how would i be able to integrate the hot-reload.js (or its effect actually) into this boilerplate? That is, how do i reload the extension when build/contentScript.bundle.js is changed?
Thanks in advance!
For who is interested. I ended up placing mentioned hot-reload.js in my extension, and loading it from within the background script. That breaks webpack's hot-reloading, by reloading the entire extension on any file-change. But as long as i only work on the content script, thats fine. I can remove it once im done, or if i work on other scripts.
Use server-sent-events:
start.js
const SSEStream = require('ssestream').default;
let sseStream;
...
setupMiddlewares: (middlewares, _devServer) => {
if (!_devServer) {
throw new Error('webpack-dev-server is not defined');
}
/** 改动:/reload path SSE */
middlewares.unshift({
name: 'handle_content_change',
// `path` is optional
path: '/reload',
middleware: (req, res) => {
console.log('sse reload');
sseStream = new SSEStream(req);
sseStream.pipe(res);
res.on('close', () => {
sseStream.unpipe(res);
});
},
});
return middlewares;
}
webpack.compiler.hook
let contentOrBackgroundIsChange = false;
compiler.hooks.watchRun.tap('WatchRun', (comp) => {
if (comp.modifiedFiles) {
const changedFiles = Array.from(comp.modifiedFiles, (file) => `\n ${file}`).join('');
console.log('FILES CHANGED:', changedFiles);
if(watchRunDir.some(p => changedFiles.includes(p))) {
contentOrBackgroundIsChange = true;
}
}
});
compiler.hooks.done.tap('contentOrBackgroundChangedDone', () => {
if(contentOrBackgroundIsChange) {
contentOrBackgroundIsChange = false;
console.log('--------- 发起 chrome reload 更新 ---------');
sseStream?.writeMessage(
{
event: 'content_changed_reload',
data: {
action: 'reload extension and refresh current page'
}
},
'utf-8',
(err) => {
sseStream?.unpipe();
if (err) {
console.error(err);
}
},
);
}
});
crx background
if(process.env.NODE_ENV === 'development') {
const eventSource = new EventSource(`http://${process.env.REACT_APP__HOST__}:${process.env.REACT_APP__PORT__}/reload/`);
console.log('--- start listen ---');
eventSource.addEventListener('content_changed_reload', async ({ data }) => {
const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
const tabId = tab.id || 0;
console.log(`tabId is ${tabId}`);
await chrome.tabs.sendMessage(tabId, { type: 'window.location.reload' });
console.log('chrome extension will reload', data);
chrome.runtime.reload();
});
}

In static template, script tag to import another file not working

I have created Field.js and index.js. index.js uses Field. However Field.js is never loading. My sandbox is below.
Is it not possible to do <script src="Field.js"></script> in codesandbox? Does it only work for src="index.js"?
Here is my sandbox - https://codesandbox.io/s/l55933j36z
Here is a sample of how we might listen for a custom event. This would allow you to 'wait' for the script and function correctly.
// This lives in you scrip dependancy.
const scriptReadyEvent = new CustomEvent('MyScriptLoaded', {
bubbles: true
});
// Dummy a delay in loading a script
function demoDelay() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 2000);
});
}
// Listen for script loaded
document.addEventListener('MyScriptLoaded', () => {
console.log('Script is loaded');
});
// The script would do this.
demoDelay().then(() => {
document.dispatchEvent(scriptReadyEvent);
});

Categories