I tried to look for answers, but couldn't find. I want to write a function to delete a previously used test organization before I start my tests in testcafe.
It's quite a time-consuming action if to make it through UI. So I wonder if it is possible to use app actions and write a function to delete my test data?
My thoughts are to perform the next steps:
1. find all test organizations I want to delete
2. iterate through each of them, call ShowDeleteOrgModal() method, and after that call DeleteOrganisation() method.
I saw other test tools provide access to application actions using window().
Is there any way I can implement it in testCafe?
The button selector looks like this.
<button class="button_class" onclick="OurApplicationName.ShowDeleteOrgModal('organisation_id');
return false;">Delete Organisation</button>
We had implemented similar idea in cypress this way:
CleanUpOrgs() {
cy.window().then((win) => {
let numberOfOrgs = win.window.$('tr:contains(' + Cypress.env('testOrgName') + ')').length;
while (numberOfOrgs > 0) {
cy.get('table').contains('tr', Cypress.env('testOrgName')).then(elem => {
let orgId = elem[0].id.replace('OurApplicationName_', '');
cy.window().then((win) => {
win.window.OurApplicationName.ShowDeleteOrgModal(orgId);
win.window.OurApplicationName.DeleteOrganisation();
cy.wait(2000);
});
});
numberOfOrgs--;
}
});
},
How can I get access to the window using TestCafe?
Try using ClientFunction. For example, you can open your modal with the following code:
import { ClientFunction } from 'testcafe';
const showDeleteOrgModal = ClientFunction(organizationId => {
OurApplicationName.ShowDeleteOrgModal(organizationId);
});
fixture`My fixture`
.page`http://www.example.com/`;
test('My Test', async t => {
await showDeleteOrgModal('organisation_id');
});
You can also run the asynchronous code on the client side this way.
UPDATE
I can't provide you with an exact test without access to the testing page.
But I've created an example how this test can look like
import { ClientFunction, Selector, t } from 'testcafe';
import { testOrgName } from './config';
fixture`fixture`
.page`http://url`;
const trElSelector = Selector('table').find('tr').withText(testOrgName);
const cleanUpOrg = ClientFunction(() => {
const trElement = trElSelector();
const orgId = trElement.id.replace('OurApplicationName_', '');
window.OurApplicationName.ShowDeleteOrgModal(orgId);
window.OurApplicationName.DeleteOrganisation();
}, { dependencies: { trElSelector } });
async function cleanUpAllOrgs () {
const numberOfOrgs = await Selector('tr').withText(testOrgName).length;
for (let i = numberOfOrgs; i > 0; i--) {
await cleanUpOrg();
await t.wait(200);
}
}
test('test', async t => {
await cleanUpAllOrgs();
});
I used ClientFunctions, Selectors and the config file for the testOrgName variable (you can learn more about using configurations and environment variables in FAQ).
Related
Below is mine scenario where in one scenario I'm getting data from page and saving it in a variable as alias.
tHen I want to use same variable/data in other scenario to put in an input field.I'm using Alias but getting this error.
cy.wait() could not find a registered alias for: #Orderinfo.
You have not aliased anything yet.
Even it alliased properly. Data stores in #Orderinfo but not accessable in other sceanrio step.
Then("Get Data from page", () => {
cy.get(".os-order-number").invoke("text").then(($Oid) => {
let Order = $Oid.text();
let Order_id = Order.replace(/[^0-9]/g, "");
cy.wrap(Order_id).as("Orderinfo");
});
});
Given("Go to Login", () => {
cy.visit("https://dev.simplifyshopping.com/register/");
});
When("Paste variable here", () => {
cy.wait(2000);
cy.wait("#Orderinfo")
cy.get("#Orderinfo")).then((Orderinfo) => {
console.log(Orderinfo);
cy.get("#id_email").type(Orderinfo);
});
});
So both, the use across several steps of the same scenario, as well as scenario overlapping are possible with Cypress using Cucumber Preprocessor.
1. Use of values across multiple steps of the same scenario
Referring to the example from the question, the Order_Id can be defined outside the steps and is thus accessible in the global scope from all steps. If I understood the code correctly, it would be something like this (probably unnecessary code commented out):
let Order_id;
Then("Get Data from page", () => {
cy.get(".os-order-number").invoke("text").then(($Oid) => {
let Order = $Oid.text();
Order_id = Order.replace(/[^0-9]/g, "");
// cy.wrap(Order_id).as("Orderinfo");
});
});
Given("Go to Login", () => {
cy.visit("https://dev.simplifyshopping.com/register/");
});
When("Paste variable here", () => {
cy.wait(2000);
// cy.wait("#Orderinfo")
// cy.get("#Orderinfo")).then((Orderinfo) => {
// console.log(Orderinfo);
// cy.get("#id_email").type(Orderinfo);
// });
console.log(Order_id);
cy.get("#id_email").type(Order_id);
});
2. Use of values across scenarios (hold state across tests)
To make certain values accessible across the execution of different scenarios, for example, a helper.js file can be created containing the following code:
export const stateStore = {};
Inside your step definition files, you can then import the stateStore and fill it with values as you like:
import { Given, When } from 'cypress-cucumber-preprocessor/steps';
import { stateStore } from '../helpers';
// step used in first scenario
Given('some value is made available in scenario 1', () => {
stateStore.someValue = 'this is a value';
});
// step used in second scenario
When('this value can be used in another step of scneario 2', () => {
console.log(`Print some value: ${stateStore.someValue}`);
});
It can be done using "alias" .as in cypress. Store variable .as("vaiableName") and then access it in required function as this.variableName. It could be like this.
Then("Get Data from page", function () {
cy.get(".os-order-number").then($Oid => {
const Order = $Oid.text()
const Order_id = Order.replace(/[^0-9]/g, "")
cy.log("inside Then function" + Order_id)
cy.wrap(Order_id).as("wrapText")
})
})
Given("Go to Login", function () {
cy.log(this.wrapText)
})
I currently have a unit test that will mock the click of a button. I've recently added a debounce function around it:
import * as lodash from 'lodash';
//...bunch of code in between
const buttonChangerTrue= lodash.debounce(() => buttonChanger(true, row), 500);
This click of the button will cause a change in the UI which will basically change the icon color (assuming the end user doesn't rapidly click it).
For the sake of brevity, I've removed the excess code for the test
this.when.push({
beforeEachRender: () => {
count++;
jest.useFakeTimers();
if (count === 1) {
this.mockHttpRequests();
} else {
this.mockHttpRequestsReversed('buttonPressed');
}
},
describe: 'When an unsaved search is ran and an agent is pinned',
then: async () => {
test('Then a note should be created', async () => {
const button = tableBody.children[1].children[1].children[0].children[0];
const stickyNoteBeforePinning =
this.getById(document.body, 'notes-69fc105ad2c94edf16efb1f4de125c38093aefe9') as HTMLElement;
if (!button ) {
throw 'button not found';
}
await wait(() => {
fireEvent.click(button);
});
jest.runTimersToTime(1000);
const stickyNoteAfterPinning =
this.getById(document.body, 'notes-ec9c2a3041a18a4a7d8d8b4943292cb8aa92a2f5') as HTMLElement;
expect(stickyNoteBeforePinning).toHaveAttribute('class', 'lead material-icons text-light');
expect(stickyNoteBeforePinning).not.toBe(stickyNoteAfterPinning);
expect(stickyNoteAfterPinning).toHaveAttribute('class', 'lead material-icons text-pink'); // Fails here
});
},
});
Please let me know if you need more information but this click of a button does make an API call, I've mocked the call in the test as well. When I remove the debounce function and run it as normal -- it passes the test just fine. I've tried a number of things like jest.useFakeTimers(); in the beforeEachRender portion and calling jest.runTimersToTime(1000); right after it. I've also tried using jest.advanceTimersByTime(500); right after the click too. Nothing seems to be working.
Edit: I ended up removing the tests for now. I read that Jest does have a version that can take into account something like jest.useFakeTimers('modern'); and that will simulate the debouncing. Will report back if I can get results on that.
You need to use useFakeTimers() from sinon
Here its an example of use:
import * as sinon from 'sinon';
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
});
afterEach(() => {
clock.restore();
});
test('debounce', () => {
// wait 1000ms
clock.tick(1000);
func() // Execute your function.
expect(func).toHaveBeenCalledTimes(1); // func called
});
I commonly use the Proxy pattern to avoid API calls
const bankAPI = () => ({
getUserData: () => { name: "Victorio" },
})
function bankAPIProxy = () => {
const bankAPI = bankAPI();
const cache = {};
getUserData = () => {
if(cache.userData) return cache.userData; // Save API Calls
cache.userData = bankAPI.getUserData();
}
}
But, can this same pattern be used to add extra functionality and still be considered a Proxy?
What about, if instead of implementing that, the Proxy looks like this:
function bankAPIProxy = () => {
const bankAPI = bankAPI();
getUserData = () => bankAPI.getUserData();
newMethod = () => "Hello World!";
}
is this still a proxy?
The proxy pattern is used so the client doesn't have to know if they are using a real object (subject) or a proxied one. This is why a proxy has to have the same interface as a subject.
This is why there should not be any additional methods - if the client relies on them, then it will be not possible to switch to the original object without additional refactor.
You can check https://en.wikipedia.org/wiki/Proxy_pattern for more information.
I'm new on electronjs and developing a small application that reads a json file and build a small html form and return the values entered by the user.
So I've developed small scripts in javascript that link to html 'button' tags to call dialogs so that a user can enter directories, files and save the final form. Everything works nicely... on electronjs "^3.1.13". But if I'm updating to a recent version of the lib ("^8.2.5"), then all my cool ShowOpenDialog don't work at all. Any clue of what happens?
Here is the script to open a folder if it helps:
{
let myName = document.currentScript.getAttribute('name');
const ipc = require('electron').ipcRenderer;
let asyncBtn = document.querySelector('#folder-selector-'+myName);
let replyField = document.querySelector('#folder-selector-content-'+myName);
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate'],
};
dialog.showOpenDialog(
dialogOptions,
fileNames => {
if (fileNames === undefined) {
console.log("No file selected");
} else {
console.log('file:', fileNames[0]);
replyField.value = fileNames[0];
}
})
};
asyncBtn.addEventListener("click", onButtonClick);
}
Thanks a lot for any help.
Apart from the fact that the call to dialog.showOpenDialog has indeed been updated in recent versions of Electron, and returns a promise instead of making use of a callback function, there is another flaw in your updated code: reading the above-mentioned documentation page shows that getCurrentWindow() is not a method of dialog; it can be obtained from remote instead, so you have to add it explicitely:
const { dialog, getCurrentWindow } = require('electron').remote;
then simply call it from inside dialog.showOpenDialog:
dialog.showOpenDialog( getCurrentWindow(), dialogOptions).then(result => {
but this is an error you could have caught yourself by looking at the DevTools's console, which would display:
TypeError: dialog.getCurrentWindow is not a function
Recent version of showOpenDialog receives two arguments: optional BrowserWindow, and options as second argument. It returns promise and not requires callback.
https://github.com/electron/electron/blob/8-x-y/docs/api/dialog.md#dialogshowopendialogbrowserwindow-options
So you need to change you callback logic to promises.
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate'],
};
dialog.showOpenDialog(
dialogOptions
).then((fileNames)=>{
if (fileNames === undefined) {
console.log("No file selected");
} else {
console.log('file:', fileNames[0]);
replyField.value = fileNames[0];
}
}).catch(err=>console.log('Handle Error',err))
};
asyncBtn.addEventListener("click", onButtonClick);
thanks a lot Vladimir. So I've tried to update my code as explained, updating electron package to version 8.2.5 and modifying the script as you explained but it's not going any better. If I got it well, this code should be correct, but doesn't work on electron 8.2.5. Any error you still see on this?
{
let myName = document.currentScript.getAttribute('name');
const ipc = require('electron').ipcRenderer;
let asyncBtn = document.querySelector('#folder-selector-'+myName);
let replyField = document.querySelector('#folder-selector-content-'+myName);
let onButtonClick = function() {
const { dialog } = require('electron').remote;
let dialogOptions = {
title: "Choisir un dossier:",
properties: ['openDirectory','promptToCreate']
};
dialog.showOpenDialog( dialog.getCurrentWindow(), dialogOptions).then(result => {
if(!result.canceled) {
replyField.value = result.filePaths[0];
}
}).catch(err => {
console.log(err)
})
};
asyncBtn.addEventListener("click", onButtonClick);
}
Ok, finally got it. Apart from the most appreciated help I had, I missed
"webPreferences": {
nodeIntegration: true
}
in the main.js to make it work.
The discovering of the Developer Tools were of great help as well :)
Now everything is fine again. Thanks a lot!
I have a function that's doing calls for firebase database and return those data. I'm trying to implement a listener to this function so when the database updates, the content in my web site also updates without refresh.
My function is as follows
export const loadBookings = async () => {
const providersSnapshot = await firebase.database().ref('products').once('value');
const providers = providersSnapshot && providersSnapshot.val();
if (!providers) {
return undefined;
}
return providers;
};
After going through some documentation i have tried changing itto something like this
const providersSnapshot = await firebase.database().ref('products').once('value');
let providers = "";
providersSnapshot.on('value', function(snapshot) {
providers = snapshot.val();
});
But the code doesn't work like that. How can i listen in real time for my firebase call?
Use on('value') instead of once('value'). once() just queries a single time (as its name suggests). on() adds a listener that will get invoked repeatedly with changes as they occur.
I suggest reading over the documentation to find an example of using on(). It shows:
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', function(snapshot) {
updateStarCount(postElement, snapshot.val());
});