I have a fairly complex test involving quite a few elements on the page, need to save the values and use them later in an assertion.
Currently I am using aliases to save the values, as per the docs recommendation. Is there a way to avoid deeply nesting like this?
For example,
cy.get(selector1).invoke('val').as('alias1')
cy.get(selector2).invoke('val').as('alias2')
cy.get(selector3).invoke('text').as('alias3')
cy.get(selector4).invoke('text').as('alias4')
cy.get(selector5).invoke('text').as('alias5')
// etc
cy.get('#alias1').then((val1) => {
cy.get('#alias1').then((val2) => {
cy.get('#alias1').then((val3) => {
cy.get('#alias1').then((val4) => {
cy.get('#alias1').then((val5)=> {
// assert values against fixture
expect([val1, val2, val3, val4, val5]).to.deep.eq(myFixture)
When you set an alias, it is also added as a property of the test context object.
You can use the function syntax to access "this" context, and read the values in a single callback at the end of the test.
cy.get(selector1).invoke('val').as('alias1')
cy.get(selector2).invoke('val').as('alias2')
cy.get(selector3).invoke('text').as('alias3')
cy.get(selector4).invoke('text').as('alias4')
cy.get(selector5).invoke('text').as('alias5')
cy.then(function() {
const actual = [this.val1, this.val2, this.val3, this.val4, this.val5]
expect(actual).to.deep.eq(myFixture)
})
Related
Alright, as the title says- i'm trying to write a custom command for a cypress test suite. The situation as as follows: I have several tests that need to select an indeterminate number of fields and select an option from the each fields list of drop downs.
The logic for this is crayons-in-mouth simple and works fine:
cy.get(selector)
.select(selection)
.scrollIntoView()
and this works great. But because I use it a lot it's a lot of highly repetitive code so I'm trying to create a custom command where I can just inject an array of arrays (various sets of selectors and selections depending on the situation) into it and it'll do the rest.
This is the custom command as I have it written now.
commands.js
Cypress.Commands.add("assignImportFields", (array) => {
cy.wrap(array).each((selector, selection) => {
cy.get(selector)
.select(selection)
.scrollIntoView()
cy.log('using ' + selector + ' to select ' + selection)
})
})
I have the data in a seperate file that looks like this:
data.js
const importFields = {
actorListImports : [
[selectors.lastName, 'Last_Name'],
[selectors.firstName, 'First_Name'],
[selectors.phoneNum, 'Phone_Number']
]
}
exports.importFields = importFields;
and finally, in my test file:
tests.js
const {actorListImports} = data.importFields;
cy.assignImportFields(actorListImports)
The response I get from this is that the 'select' failed because it requires a dom element. My selectors are fine, so I think it's trying to use an entire array (both selector and selection at once) as the selector instead of the first part of the array.
I know i'm not structuring the data correctly, but i've tried a few different variations of it and my primitive monkey brain just can't put together.
Can someone help me identify what's wrong with how i've structure this?
You need to de-structure the array elements in the .each() parameter list, like this cy.wrap(data).each(([selector, selection])
This is a minimal example:
const selectors = {
'lastName': 'lastName'
}
const data = [
[selectors.lastName, 'Last_Name'],
// [selectors.firstName, 'First_Name'],
// [selectors.phoneNum, 'Phone_Number']
]
cy.wrap(data).each(([selector, selection]) => {
console.log(selector, selection)
expect(selector).to.eq('lastName') // passing
expect(selection).to.eq('Last_Name') // passing
})
i thought i got the hang of dexie, but now i'm flabbergasted:
two tables, each with a handful of records. Komps & Bretts
output all Bretts
rdb.Bretts.each(brett => {
console.log(brett);
})
output all Komps
rdb.Komps.each(komp=> {
console.log(komp);
})
BUT: this only outputs the Bretts, for some weird reason, Komps is empty
rdb.Bretts.each(brett => {
console.log(brett);
rdb.Komps.each(komp=> {
console.log(komp);
})
})
i've tried all kinds of combinations with async/await, then() etc, the inner loop cannot find any data in the inner table, whatever table i want to something with.
2nd example. This Works:
await rdb.Komps.get(163);
This produces an error ("Failed to execute 'objectStore' on 'IDBTransaction…ction': The specified object store was not found.")
rdb.Bretts.each(async brett => {
await rdb.Komps.get(163);
})
Is there some kind of locking going on? something that can be disabled?
Thank you!
Calling rdb.Bretts.each() will implicitly launch a readOnly transaction limited to 'Bretts' only. This means that within the callback you can only reach that table. And that's the reason why it doesn't find the Comps table at that point. To get access to the Comps table from within the each callback, you would need to include it in an explicit transaction block:
rdb.transaction('r', 'Komps', 'Bretts', () => {
rdb.Bretts.each(brett => {
console.log(brett);
rdb.Komps.each(komp=> {
console.log(komp);
});
});
});
However, each() does not respect promises returned by the callback, so even this fix would not be something that I would recommend either - even if it would solve your problem. You could easlily get race conditions as you loose the control of the flow when launching new each() from an each callback.
I would recommend you to using toArray(), get(), bulkGet() and other methods than each() where possible. toArray() is also faster than each() as it can utilize faster IDB Api IDBObjectStore.getAll() and IDBIndex.getAll() when possible. And you don't nescessarily need to encapsulate the code in a transaction block (unless you really need that atomicy).
const komps = await rdb.Komps.toArray();
await Promise.all(
komps.map(
async komp => {
// Do some async call per komp:
const brett = await rdb.Bretts.get(163));
console.log("brett with id 163", brett);
}
)
);
Now this example is a bit silly as it does the exact same db.Bretts.get(163) for each komp it founds, but you could replace 163 with some dynamic value there.
Conclusion: There are two issues.
The implicit transaction of Dexie's operation and the callback to each() lives within that limited transaction (tied to one single table only) unless you surround the call with a bigger explicit transaction block.
Try avoid to start new async operation within the callback of Dexie's db.Table.each() as it does not expect promises to be returned from its callback. You can do it but it is better to stick with methods where you can keep control of the async flow.
I want to get the 'Created On Date' from the datatable to find out which timezone it falls into?
I was searching for a particular campaign using filter. But I am not sure how to extract a text from the datatable.
cy.get('input[placeholder="Filter..."]:nth(2)').type("campaign1");
cy.get("tbody")
.contains("campaign1")
.closest("tr")
.should("contain.text", this.Advertiser)
.should("contain.text", this.Brand)
.then(text => {
const rowText = text;
});
}
But I got this response from Cypress
CypressError: Timed out retrying: expected '<tr.MuiTableRow-root>' to contain text undefined, but the text was 'Advertiser UKBrand UKcampaign14 Nov 2019'
How do I extract just the date from the datable?
You are on the correct way, but you're not searching deep enough. This should help:
cy.get('input[placeholder="Filter..."]:nth(2)').type("campaign1");
cy.get("tbody")
.contains("campaign1")
.closest("tr")
.should("contain.text", this.Advertiser)
.should("contain.text", this.Brand)
.find("td")
.eq(5)
.then(text => {
const rowText = text;
});
You did find the correct row, but not the correct cell. By adding the find("td") it does search for the cells. And the .eq(5) actually selects the 5th occurence of the td, which is the cell for the Created On column
What I can understand from your question is you just want to grab the date values and make an assertion on that.
Assumption Made: With filter applied you always get results and you want to write tests only for the cell with date.
This might help you:
function getTextOfCell(rowIndex){
cy
.get('tbody.MultiTableBody-root')
.get('tr.MultiTableRow-root')
.eq(rowIndex)
.find('td').eq(3).invoke('text').then((txt)=>{
cy.log(txt)
})
}
describe('Test Test', ()=>{
it('Test Test', ()=>{
cy.visit('yourURl')
cy.get('tbody.MultiTableBody-root tr.MultiTableRow-root').its('length').then((rowLength)=>{
for(let i=0; i<rowLength; i++){
getTextOfCell(i)
}
});
})
})
Note:
I am assuming that sometimes you will have more than one row as
result. So iterating over all the available rows.
And the date cell will have always have the index 3 for any
row.
In function getTextOfCell we are grabbing the text of the cell
and saving it in txt. Here I am logging out only. You can
make your assertion here.
The problem is that this.Advertiser is undefined when the should function is called. You didn't provide the piece of code where this member is initialed, but I guess that it's initialed inside a then or it's a property. Anyway, like most Cypress methods, when should is called, it doesn't actually performs the validation, but only queues a command that will be executed later to perform the validation. This means that when should is called, this.Advertiser is still undefined and this gets passed as an argument to theshould function, even though when the should command is executed later on, this.Advertiser has a valid value.
The solution should be, instead of using this.Advertiser, put the entire code block that appears in your question, inside the then block where the value in which this.Advertiser is initialized, and use the parameter passed to this then block instead. It should look something like this:
cy.somethingThatProvidesAdvertiser().then(advertiser => {
cy.get('input[placeholder="Filter..."]:nth(2)').type("campaign1");
cy.get("tbody")
.contains("campaign1")
.closest("tr")
.should("contain.text", advertiser)
.should("contain.text", this.Brand)
.find("td")
.eq(5)
.then(text => {
const rowText = text;
});
});
I guess that you should do the same for this.Brand, which means that you should have the 2 then clauses nested.
I hope I managed to explain it clear enough...
Thanks All for the responses. The below code helped me to fetch the desired value from the datatable.
const txt = [];
cy.get("tbody")
.contains("campaign1")
.parent()
.next("td")
.invoke("text")
.then(x => {
txt.push(x);
});
cy.log((this.txt = txt));
I'd suggest that you assign a cypress-dedicated id on a row/a column to easily extract data and tests are more maintainable.
<tr data-cy='campaign-${id}' ...>
<td data-cy='created-on'>
....
<td>
</tr>
For any given campaign, you can get its text with ease and code is readable.
cy.get('[data-cy=campain-3]').find('[data-cy=created-on]').invoke('text')
I am trying to use a Laravel collection to return a groupBy as an array. However, it always seems to be returned as an object no matter what. I have tried to do $posts->groupBy('category')->toArray() but this seems to still return as an object. I have also tried $posts->groupBy('category')->all() and still it is returning as an object.
I don't know if this is something to do with Laravel returning methods within the routes, but I need this to return as an array.
Here is the code:
public function getFeatures($id)
{
return Feature::query()->get()->groupBy('category')->toArray();
}
The actual code is working fine and I'm getting results back but it just doesn't seem to be converting to an array. Thanks.
When doing a query to get (possibly) several items using Eloquent, Laravel will always return an instance of the Collection class that contains all the model objects. If you need them converted to array to use them in a view you could compact the elements. compact will make an associative array of the elements of the collection:
public function getFeatures($id)
{
$features = Feature::all();
return view('my_cool_view', compact($features));
}
On the other hand, if you need them converted to array to return them through an API, Laravel convert the response to JSON by default:
public function getFeatures($id)
{
return Feature::all();
}
Now, if you somehow need the collection converted to an array, just use toArray() like you indicated:
public function getFeatures($id)
{
$collection_of_features = Feature::all();
$array_of_features = $collection_of_features->toArray();
// use it for wherever you want.
}
By reading your comment on other answer, I realized what you mean.
Hi #HCK, thanks for the answer. The ->toArray() method doesn't seem to work and still returns it like { "category_1": [], "category_2": [] } whereas I need it to do ["category_1": [], "category_2": []]
First, this answer is based on a guess that you are doing something like this on your controller (you didn't posted the controller code):
return reponse()->json($theResponseFromGetFeaturesMethod);
Since inside php the $theResponseFromGetFeaturesMethod variable contains an dictionary array (something like ["key"=>"value]), when you convert it to a JSON, you will notice that this "conversion" happens.
This happens because Javascript arrays (and JSON) doesn't support dictionary arrays. See this sample on javascript console:
> var a = [];
undefined
> a['key'] = "value"
"value"
> a
> key: "value"
length: 0
__proto__: Array(0)
Note that the a still have a length of zero, but it now have an key property.
This happens because almost everything on javascript is actually an object. So the array is a special kind of object that have push, pop and many other array methods. Doing array[] = 'somevalue' is actually a shortcut to array.push('somevalue').
So, the behavior that you are observing is right, the toArray() method work as expected, and the JSON conversion too.
Another weird behavior is when you try to convert this PHP array to an JSON:
[
0 => 'a'
1 => 'b'
9 => 'c'
]
You will note that in this case, PHP will convert this array to an object too. The result in JSON will be:
{
"0": "a",
"1": "b",
"2": "c"
}
This is also the expected behavior, since the JSON syntax doesn't support defining the index for a value.
I want to do something like
from table1
where col5="abcd"
select col1
I did like
query_ = From g In DomainService.GetGEsQuery Select New GE With {.Desc = g.codDesc}
"This cause a runtime error, i tried various combinations but failed"
please help.
I'm assuming your trying to do this on the client side. If so you could do something like this
DomainService.Load(DomainService.GetGEsQuery().Where(g => g.codDesc == "something"), lo =>
{
if (lo.HasError == false)
{
List<string> temp = lo.Entities.Select(a => a.Name).ToList();
}
}, null);
you could also do this in the server side (which i would personally prefer) like this
public IQueryable<string> GetGEStringList(string something)
{
return this.ObjectContext.GE.Where(g => g.codDesc == something).Select(a => a.Name);
}
Hope this helps
DomainService.GetGEsQuery() returns an IQueryable, that is only useful in a subsequent asynchronous load. Your are missing the () on the method call, but that is only the first problem.
You can apply filter operations to the query returned using Where etc, but it still needs to be passed to the Load method of your domain context (called DomainService in your example).
The example Jack7 has posted shows an anonymous callback from the load method which then accesses the results inside the load object lo and extracts just the required field with another query. Note that you can filter the query in RIA services, but not change the basic return type (i.e. you cannot filter out unwanted columns on the client-side).
Jack7's second suggestion to implement a specific method server-side, returning just the data you want, is your best option.