I read the caveats in the docs on Cypress conditional testing, but nevertheless need to apply it to a particular test for certain reasons.
I have a function to do it, but there are certain selectors that do not work due to lack of retry in this function.
How can I implement retry in conditional testing and avoid flaky tests?
Is it even possible, or does one thing cancel out the other?
export function elementExists(selector: string): boolean {
try {
return Cypress.$(selector).length > 0;
} catch (error) {
return false;
}
The "standard" way to test existence of an element is pretty simple, but it does not return true/false. It fails the test if element is not found.
cy.get(selector).should('exist')
Internally the .should() retries the element until command timeout is finished - then fails the test.
If you make your function recursive, you can do the same but instead of failing, return true/false.
function elementExists(selector, attempt = 0) {
const interval = 100; // 100ms between tries
if (attempt * interval > Cypress.config('defaultCommandTimeout')) {
cy.log(selector, 'not found')
return cy.wrap(false, {log:false})
}
return cy.get('body', {log:false}).then(($body) => {
const element = $body.find(selector)
if (element.length) {
cy.log(selector, 'found')
return cy.wrap(true, {log:false})
} else {
cy.wait(interval, {log:false})
return elementExists(selector, ++attempt)
}
})
}
elementExists(selector).then(exists => {
if (exists) {
...
}
})
It's even easier now with the cypress-if package.
But retry is implemented asynchronously, so you will have to return a Chainable.
export function elementExists(selector: string): Chainable<boolean> {
return cy.get(selector)
.if('exist')
.then(true)
.else()
.then(false)
}
elementExists('span#123').then((result: boolean) =>
if (result) {
...
}
})
The above uses the full API and is very readable, but this should also work for you
export function elementExists(selector: string): Chainable<JQuery<HTMLElement>|undefined> {
return cy.get(selector).if()
}
elementExists('span#123').then((result: JQuery<HTMLElement>|undefined) =>
if(result.length) {
...
}
})
Related
I have some input fields on a page. They can be disabled or not. So I need to type the text in this field just in case when it's not disabled. I try to do this way:
fillPickUpTown(pickUpTown: string) {
cy.get(`[data-testid="pick-up-town"]`)
.should('not.be.disabled')
.then(() => {
cy.get(`[data-testid="pick-up-town"]`).type(pickUpTown);
});
}
But I have failed test with error "Timed out retrying after 10000ms: expected '' not to be 'disabled'".
How can I type to this field just when it's not disabled and do nothing when it is?
Drop the should, this will only succeed for the one condition. Instead, test inside then()
fillPickUpTown(pickUpTown: string) {
cy.get(`[data-testid="pick-up-town"]`)
.then($el => {
if (!$el.is(':disabled')) {
cy.wrap($el).type(pickUpTown);
}
});
}
You can use JQuery :enabled to check and implement If-Else.
fillPickUpTown(pickUpTown: string) {
cy.get(`[data-testid="pick-up-town"]`).then(($ele) => {
if ($ele.is(":enabled")) {
cy.get(`[data-testid="pick-up-town"]`).type(pickUpTown)
}
})
}
I'm unit testing and part of the testing has a Subject. I'm new to Subjects and get the general gist of how they work but am struggling to mock a return value on one. I've tried various ways in the hopes of stumbling on the correct way like using a spy and returnvalue to return the number 3.
In the component:
....
private searchEvent: Subject<string> = new Subject<string>();
....
this.searchEvent.pipe(debounceTime(500)).subscribe(value => {
if (value.length >= 3) {
this.retrieveAssets(value);
}
})
....
In my spec file I basically have:
component['searchStockEvent'].subscribe(x=> of(3));
fixture.whenStable().then(() => {
expect(component['retrieveAssets']).toHaveBeenCalled();
});
searchEvent being private will make it difficult to call next directly on the subject so you have to find a way of what makes searchEvent emit a value of greater than 3 and go that route.
For this demo, we will make it public:
....
public searchEvent: Subject<string> = new Subject<string>();
....
this.searchEvent.pipe(debounceTime(500)).subscribe(value => {
if (value.length >= 3) {
this.retrieveAssets(value);
}
})
....
import { fakeAsync, tick } from '#angular/core/testing';
it('should call retrieve assets', fakeAsync(() => {
component.searchEvent.next(3);
// we need to pass 501ms in a fake way
tick(501);
expect(component.retreiveAssets).toHaveBeenCalled();
}));
Hi all I want to use expo-sqlite to transaction object to execute an sql statement.
However, I got a problem in define the return value of the error function.
Here is the example code:
tx.executeSql(
// sql statement (ok)
"...",
// input arguments (ok)
[...],
// success case: since I use it in a promise, so I use: resolve(...) (ok)
() => {
resolve()
},
// failed case: I want to reject it, use reject()
// But I got an Error here! (Wrong!!!)
// Here ask me to return a boolean value, but how??? true or false???
(_, err) => {
reject(err) // not enough???
}
)
From the type definition file, I know, I need to return a boolean value for the error callback function, but which one? true or false???
Do you have some idea how to do it???
PS. Here is official doc about the expo-sqlite: https://docs.expo.io/versions/latest/sdk/sqlite/
I don't know why the error callback function does need a return type of boolean. Since we resolve/reject the promise anyways I think we can savely ignore the return type.
Below you can find my typescript synchronous example:
export const fetchTypeSaveSql = async <T>(sqlStatement: string, args: any[] | undefined): Promise<T[]> => {
return new Promise((resolve) => {
db.transaction(tx => {
tx.executeSql(
sqlStatement, args,
(_, result) => {
resolve(Array.from(result.rows as any) as T[])
},
(_, error): boolean => {
console.warn(error)
resolve([])
return false
})
})
})
}
The return type of SQLStatementErrorCallback is boolean (and not void) because it's used to indicate whether the error was handled or not.
If the error is handled (ie return true), the whole transaction doesn't fail. If it's not handled (ie return false), then it does. You should only return true if you've been able to suitably recover from the error.
Remember that executeSql is only used within a transaction (which is created via db.transaction or db.readTransaction). A transaction accepts it's own success and error callbacks.
You can check the this in the source code by working backwards from this: https://github.com/nolanlawson/node-websql/blob/7b45bf108a9cffb1c7e16b9a7dfec47be8361850/lib/websql/WebSQLTransaction.js#L64-L68
if (batchTask.sqlErrorCallback(self, res.error)) {
// user didn't handle the error
self._error = res.error;
return onDone();
}
I have the following bit of code:
Which prints the following in the console:
I've been bashing my head for a very long time, not sure where to go from here. It was working just fine when I pushed last. Then, I made some changes which broke it as you can see. To try to fix it, I stashed my changes, but I'm still getting this error.
Edit
search: throttle(live => {
let vm = this;
console.log("entered!!!");
console.log("this", this);
console.log("vm", vm);
if (typeof live == "undefined") {
live = true;
}
if (!live) {
// We are on the search page, we need to update the results
if (vm.$route.name != "search") {
vm.$router.push({ name: "search" });
}
}
vm.$store.dispatch("search/get", {
type: vm.searchType,
query: vm.searchQuery
});
}, 500)
Assuming search is in your methods it should not be using an arrow function as that will give you the wrong this binding.
Instead use:
methods: {
search: throttle(function (live) {
// ...
}, 500)
}
Here I'm also assuming that throttle will preserve the this value, which would be typical for implementations of throttling.
Like I said in my comment, I suspect this is a scoping issue.
Perhaps if you return the throttle function with the Vue component passed in, you might see better results:
search: function() {
let vm = this;
return throttle(live => {
console.log("entered!!!");
console.log("this", this);
console.log("vm", vm);
if (typeof live == "undefined") {
live = true;
}
if (!live) {
// We are on the search page, we need to update the results
if (vm.$route.name != "search") {
vm.$router.push({ name: "search" });
}
}
vm.$store.dispatch("search/get", {
type: vm.searchType,
query: vm.searchQuery
});
}, 500)
}
I'm trying to look for an absence of an element in a conditional which would then take two different paths if the element is not there. However what I am getting is 'element not found' which is what I need but I need to go around this. Here is what I've tried:
if (HomeScreen.tabs.propertiesTab.isPresent()) {
HomeScreen.tabs.propertiesTab.click();
} else {
HomeScreen.tabs.allTabsTab.click().then(function() {
HomeScreen.allTabs.properties.click();
})
}
and
HomeScreen.tabs.propertiesTab.isPresent().toBeFalsy().then(function(isVisible) {
if (isVisible) {
HomeScreen.tabs.propertiesTab.click();
} else {
HomeScreen.tabs.allTabsTab.click().then(function() {
HomeScreen.allTabs.properties.click();
});
}
});
Any suggestions?
Try to explicitly resolve the promise with then():
browser.isElementPresent(HomeScreen.tabs.propertiesTab).then(function (isPresent) {
if (isPresent) {
// ...
} else {
// ...
}
});
Using browser.isElementPresent() here, but it should work with .isPresent() as well:
In protractor, browser.isElementPresent vs element.isPresent vs element.isElementPresent