I have a command that overwrites pause to add the input from a dialog to the reports. I want to know if there is a way to know what test is calling the function so that I can customize the message on the inputs.
Cypress.Commands.overwrite('pause', (originalFn, element, options) => {
var tryThis = '';
if (//place calling the function == file1) {
tryThis = 'message1';
} else if (//place calling the function == file2) {
...
} else if (//place calling the function == file3) {
...
}
var datalog = window.prompt(tryThis, "Log your results");
cy.addContext("DATALOG:" + datalog);
return originalFn(element, options)
})
As well as access via the Mocha properties there is also
For the spec file Cypress.spec
Properties for my.spec.js
Cypress.spec.absolute: "C:/.../my.spec.js"
Cypress.spec.name: "my.spec.js"
Cypress.spec.relative: "cypress\integration\my.spec.js"
Cypress.spec.specFilter: "my"
Cypress.spec.specType: "integration"
For the test cy.state('runnable')
For
describe('my-context', () => {
it('my-test', () => {
Properties and methods,
const title = cy.state('runnable').title; // "my-test"
const fullTitle = cy.state('runnable').fullTitle(); // "my-context my-test"
const titlePath = cy.state('runnable').titlePath(); // ["my-context", "my-test"]
You can also add metadata to the test
describe('my-context', () => {
it('my-test', { message: "my-message" }, () => {
and grab it in the command overwrite
const message = cy.state('runnable').cfg.message; // "my-message"
I tried this and it worked for me (my version of cypress is 6.1.0):
cy.log(Cypress.mocha.getRunner().suite.ctx.test.title);
More info: https://github.com/cypress-io/cypress/issues/2972
Related
I've got 3 commands i am calling from the front end, 2 of them work perfectly, the third does not.
The issue lies with the function tournament_search
main.rs:
fn main() {
tauri::Builder::default()
.manage(ApiKey {key: Default::default()})
.invoke_handler(tauri::generate_handler![set_api_key, check_connection, tournament_search])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn set_api_key(key: String , state: State<ApiKey>){
let mut api_key = state.key.lock().unwrap();
*api_key = key;
}
#[tauri::command]
async fn check_connection(api_key: State<'_, ApiKey>) -> Result<bool, ()> {
let key = api_key.key.lock().unwrap().clone();
let res = Client::new().get(API_URL).bearer_auth(key).send().await.unwrap().text().await.unwrap();
let json: Value = serde_json::from_str(res.as_str()).unwrap();
match json["success"].as_bool() {
Some(_x) => Ok(false),
None => Ok(true)
}
}
#[tauri::command]
async fn tournament_search(search_string: String, api_key: State<'_, ApiKey>) -> Result<&str, ()> {
println!("test: {}", search_string);
let key = api_key.key.lock().unwrap().clone();
let mut query: String = String::new();
query.push_str("query($name:String){tournaments(query:{filter:{name:$name}}){nodes{name,slug,id}}},{$name:");
query.push_str(search_string.as_str());
query.push_str("}");
let res = Client::new().get(API_URL).bearer_auth(key).body(query).send().await.unwrap().text().await.unwrap();
println!("{}", res);
Ok("")
}
index.js:
const { invoke } = window.__TAURI__.tauri
window.addEventListener("load", (ev) => {
let test = document.getElementById("test");
let apiKey = document.getElementById("apiKey");
let tournamentSearch = document.getElementById("tournamentSearch");
let tourneyList = document.getElementById("tourneyList");
apiKey.addEventListener("input", (ev) => {
invoke("set_api_key", {key: apiKey.value});
invoke("check_connection").then((res) => {
if(res){
tournamentSearch.disabled = false;
}else{
tournamentSearch.disabled = true;
}
});
});
tournamentSearch.addEventListener("input", (ev) => {
test.innerText = "e";
invoke('tournament_search', {search_string: tournamentSearch.value}).then((res) => {
test.innerText = res;
});
});
});
Already looked for zero width characters, whether the event get's called in js etc. The issue is just that the function is not called.
You'd only see an error message by adding a .catch() to the invoke call.
Anyawy, the issue here is that Tauri converts command arguments to camelCase on the rust side (to match the JS default) so it would be { searchString: tournamentSearch.value } instead.
If you'd prefer snake_case instead, you can tell Tauri to use that for arguments by changing the command like this:
#[tauri::command(rename_all = "snake_case")]
How do I convert this plugin that worked on Webpack 4 to Webpack 5?
More specifically, the plugin() function no longer works. How do I replace this to support Webpack 5?
const ConstDependency = require('webpack/lib/dependencies/ConstDependency');
const NullFactory = require('webpack/lib/NullFactory');
class StaticAssetPlugin {
constructor(localization, options, failOnMissing) {
this.options = options || {};
this.localization = localization;
this.functionName = this.options.functionName || '__';
this.failOnMissing = !!this.options.failOnMissing;
this.hideMessage = this.options.hideMessage || false;
}
apply(compiler) {
const { localization } = this;
const name = this.functionName;
compiler.plugin('compilation', (compilation, params) => {
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
});
compiler.plugin('compilation', (compilation, data) => {
data.normalModuleFactory.plugin('parser', (parser, options) => {
// should use function here instead of arrow function due to save the Tapable's context
parser.plugin(`call ${name}`, function staticAssetPlugin(expr) {
let param;
let defaultValue;
switch (expr.arguments.length) {
case 1:
param = this.evaluateExpression(expr.arguments[0]);
if (!param.isString()) return;
defaultValue = param = param.string;
break;
default:
return;
}
let result = localization(param);
const dep = new ConstDependency(JSON.stringify(result), expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
});
});
}
}
module.exports = StaticAssetPlugin;
Are there any migration guides for plugin creation that I can follow? Any help would be greatly appreciated.
Thanks.
You can find suitable environment details needed to run the plugin here.
Along with this, you must care about how to access event hooks
compiler.hooks.someHook.tap('MyPlugin', (params) => {
/* ... */
});
You can get more about it here
Converting your existing plugin to Webpack 5, you can tap a specific event hook and get it done.
If you try to run the plugin with the above code with Webpack 5, you will get the below error.
Many articles will suggest you update webpack-cli which is not enough.
const ConstDependency = require('webpack/lib/dependencies/ConstDependency');
const NullFactory = require('webpack/lib/NullFactory');
const PLUGIN_NAME = 'StaticAssetPlugin';
class StaticAssetPlugin {
constructor(localization, options, failOnMissing) {
this.options = options || {};
this.localization = localization;
this.functionName = this.options.functionName || '__';
this.failOnMissing = !!this.options.failOnMissing;
this.hideMessage = this.options.hideMessage || false;
}
apply(compiler) {
const { localization } = this;
const name = this.functionName;
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, params) => {
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
});
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation, data) => {
data.normalModuleFactory.hooks.parser.for('javascript/auto').tap(PLUGIN_NAME, (parser, options) => {
parser.hooks.expression.for('this').tap(PLUGIN_NAME, function staticAssetPlugin(expr) {
let param;
let defaultValue;
switch (expr.arguments.length) {
case 1:
param = this.evaluateExpression(expr.arguments[0]);
if (!param.isString()) return;
defaultValue = param = param.string;
break;
default:
return;
}
let result = localization(param);
const dep = new ConstDependency(JSON.stringify(result), expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
})
});
}
}
module.exports = StaticAssetPlugin;
Importantly you have to decide which event hook you need to access from compiler and parser. You will get a list of popular hooks here, for the compiler for parser.
You can get a complete list of hooks just by accessing hooks.
for compiler
console.log(compiler.hooks);
for parser
console.log(parser.hooks);
You can choose accordingly.
I'm using cypress runner to execute the spec file and return the result as like below.
Runner.js:
const cypress = require('cypress');
const param = require("./cypress/support/Param");
async function testRunner(fixture) {
return cypress.run({
config: {
},
env: {
testcaseID: `${fixture}`,
},
spec: './cypress/integration/' + `${param.getSpec()}` + ".spec.js",
});
}
Spec file:
let map = new Map();
describe("How to add map values in the cypress result.json",() =>{
const baseUrl = "https://www.google.com/";
const testData = Cypress.env('fixture')
beforeEach("",()=>{
cy.visit(baseUrl);
});
it("Test Case1: Search the keyword", function () {
cy.xpath("//input[#name='q']").type(testData.searchKeyword);
map.set("UserInput",testData.searchKeyword); //It's just sample
cy.xpath("//input[#value='Google Search']").click();
map.set("customMessage","test"); //It's just sample but actual usecase is different
cy.get("//ul/li[2]").should("be.visible");
});
});
using the below lines to get the result in the main runner:
result = await testRunner(dataSet[i]);
if (result.runs[0].stats.failures === 1) {
console.log(result);
}
The above code is working fine and I can able to get the result.json which contains whether the test case is pass/failed. But In addition I just want to add few more runtime values which I stored in the Map and wanna add those in the Cypress.run return result.
Can someone please suggest me the optimized way to handle this? How can I get some runtime values which is available inside the map and that will be returned along with cypress run result.
Thanks in advance.
Updated:
I just created a Map() variable and calling that variable by using the getter and setter but still no luck.
var EnumPojo = {
LISTENER: new Map(),
get listener()
{
return this.LISTENER;
},
set listener(value)
{
return this.listener =value;
};
}
Called the above map into script:
const runtimeValues = require("../../EnumPojo.js");
describe("How to add map values in the cypress result.json",() =>{
const baseUrl = "https://www.google.com/";
const testData = Cypress.env('fixture')
beforeEach("",()=>{
cy.visit(baseUrl);
});
it("Test Case1: Search the keyword", function () {
cy.xpath("//input[#name='q']").type(testData.searchKeyword);
runtimeValues.LISTENER.set("UserInput",testData.searchKeyword); //It's just sample
cy.xpath("//input[#value='Google Search']").click();
runtimeValues.LISTENER.set("customMessage","test"); //It's just sample but actual usecase is different
cy.get("//ul/li[2]").should("be.visible");
});
});
Runner:
const runtimeValues = require("../../EnumPojo.js");
result = await testRunner(dataSet[i]);
if (result.runs[0].stats.failures === 1) {
console.log(result);
console.log(runtimeValues.LISTENER);
}
Output:
Map{0}
In test, save the map under fixtures
it('my-test', function () {
...
cy.fixture('map.json').then(mapData => {
mapData['my-test'] = JSON.stringify(map) // use unique key for this test
cy.writeFile('./fixtures/map.json', JSON.stringify(mapData))
})
})
In runner,
const fs = require('fs')
cypress.run({...})
.then(result => {
const mapData = fs.readFileSync('./cypress/fixtures/map.json') // adjust path to where script is
/*
mapData is {
'my-test': { ...data here },
'other-test': { ...data here },
}
*/
})
BUT Javascript Map may not serialize correctly, best to convert to object before saving.
I just moved from TestCafe to Cypress and couldn't find a solution to abstract a common frequently used method. In this example below cy.document().then(doc).. is used twice, however I believe that these types of function must be abstracted to reusable function.
it('Test the input text field and submit button list the basket items', () => {
const allNameBeforeInput = []
const allNameAfterInput = []
cy.document().then((doc) => {
const elements = doc.querySelector('#items').querySelectorAll('.row-style > :nth-child(1)')
for (let i = 0; i <= elements.length - 1; i++) {
const basketName = elements[i].textContent
if (basketName && basketName !== '') {
allNameBeforeInput.push(`${basketName}`)
}
console.log(allNameBeforeInput.length) //this gives 0
}
})
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get('#items').children('.row-style').children('.list-item')
.contains('Suraj')
cy.document().then((doc) => {
const elements = doc.querySelector('#items').querySelectorAll('.row-style > :nth-child(1)')
for (let i = 0; i <= elements.length - 1; i++) {
const basketName = elements[i].textContent
if (basketName && basketName !== '') {
allNameAfterInput.push(`${basketName}`)
}
}
console.log(allNameAfterInput.length) //this gives 3
expect(allNameBeforeInput.length).equal(0)
expect(allNameAfterInput.length).equal(3)
expect(allNameBeforeInput.length).is.lt(allNameAfterInput.length)
})
})
This is what I want to accomplished with class Basket:
getAllBasketName() {
cy.document().then((doc) => {
const allName = []
const elements = doc.querySelector('#items').querySelectorAll('.row-style > :nth-child(1)')
for (let i = 0; i <= elements.length - 1; i++) {
const basketName = elements[i].textContent
if (basketName && basketName !== '') {
allName.push(`${basketName}`)
}
}
return allName
})
}
Now I should be able to use
const getAllBasketNamesBefore = basket.getAllBasketName()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
const getAllBasketNamesAfter = basket.getAllBasketName()
{Assertion goes here}
This is not working because of async/await is not handled so the value of before and after are alway 0. Any clue or help will be appreciated.
The method you are using is not recommended by cypress and is considered an anti-pattern. https://docs.cypress.io/guides/references/best-practices.html#Assigning-Return-Values
Cypress recommends that you add custom commands. https://docs.cypress.io/api/cypress-api/custom-commands.html#Syntax
In the initially created folder structure, the commands.js file can be found under the support folder. Here you are able to create a command that wraps up the logic you wish to reuse. Based on the console.log portion of your code I assume this is run on the command line. There are custom commands for the console as well as for use in the UI.
for that portion you may have to add this custom command
// not a super useful custom command
// but demonstrates how subject is passed
// and how the arguments are shifted
Cypress.Commands.add('console', {
prevSubject: true
}, (subject, method) => {
// the previous subject is automatically received
// and the commands arguments are shifted
// allow us to change the console method used
method = method || 'log'
// log the subject to the console
console[method]('The subject is', subject)
// whatever we return becomes the new subject
//
// we don't want to change the subject so
// we return whatever was passed in
return subject
})
For the other functionality creating commands is pretty simple, the basic pattern is:
Cypress.Commands.add(name, callbackFn)
so you can potentially create something like
Cypress.Commands.add(allNameBeforeInput, (options, options) => {
//custom logic goes here
})
Then you can use it by calling cy.allNameBeforeInput(options, options).
For instance, I was struggling with login and all my tests had login functions to log in through the UI but I wanted to start my tests on the correct page instead of the the log in page. I added this to the command.js file in the support folder:
Cypress.Commands.add('login',(username="notsharingmyusernam#stackexchange.com",
password="somesecurepasswordshhh") => {
cy.request({
method: "POST",
url: "/api/public/login",
body: `{:person/email "${username}", :person/password "${password}"}`,
headers: {
"Accept": "application/edn",
"Content-Type": "application/edn"
}
})
})
And now I can add the cy.login and a beforeEach function at the beginning of my tests. The before each to make the request to the server and wait for the request for login and the cy.login custom command to ensure that I can use that bundled up logic with just one cy command.
describe('Test suite for page traverse', () => {
beforeEach(() => {
cy.server()
cy.route("POST","/api/graphql").as("graphql")
Cypress.Cookies.preserveOnce("company_jwt_qa")
})
it('traverses all subnav items', () => {
cy.login()
cy.visit('/index.html')
cy.wait("#graphql")
cy.get('[data-tag-component="subnav-group"]')
cy.get('[data-tag-component="subnav-title"]').eq(1).click()
})
})
I am trying to write a polling method that polls a server periodically to check whether a zip file has already been created or not.
What I want to accomplish are the following:
Calls(ajax) an API that creates a zip file on server
Calls(ajax) another API that checks if the zip file has already been created (polling method)
Some subsequent process
Here is my code snippet ↓
var success: boolean = false;
//1. requests a server to create a zip file
this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
.then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.status[0].statusCode == "000") {
success = true;
} else {
//Error
}
}).then(() => {
if (success) {
//2. polls the server to check if the zip file is ready
<- Polling method↓ ->
this.polling(params).then((zipUrl) => {
console.log(zipUrl); //always logs zipUrl
//some subsequent process...
});
}
});
Could anyone give some examples of polling method that would work in this case?
Added:
private polling(params: any): ng.IPromise<any> {
var poller = () => this.apiRequest.polling(params, ApiUrl.URL_FOR_POLLING);
var continuation = () => poller().then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.zipFilePath == "") {
return this.$timeout(continuation, 1000);
} else {
return apiRes.zipFilePath;
}
})
var result: ng.IPromise<any> = continuation();
return result;
}
Basically abstract the methods out as shown below:
let poll = () => this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
let continuation = () => poll().then((/*something*/)=> {
/*if still bad*/ return continuation();
/*else */ return good;
})
continuation().then((/*definitely good*/));
Update
As requested in the comment below:
return this.$timeout(continuation, 1000);
This is needed to get angular to kick off a digest cycle.