How to use onRun method in Cypress

Best JavaScript code snippet using cypress

test-scheduler.js

Source:test-scheduler.js Github

copy

Full Screen

1var assert = require ('assert');2var sinon = require ('sinon');3var Scheduler = require('../lib/scheduler');4describe ('scheduler', function(){5 var scheduler;6 var stub;7 var clock;8 var mockJobWorker;9 beforeEach(function(done){10 clock = sinon.useFakeTimers();11 mockJobWorker = {12 widget_item: { job: 'dummy' },13 config:{interval: 3000},14 onRun : function(){},15 pushUpdate: function(){},16 dependencies : {17 logger : {18 warn: function (){},19 error: function (){},20 log: function (){}21 }22 }23 };24 stub = sinon.stub(mockJobWorker, "onRun", function(config, dependencies, cb) {25 cb(null, {});26 });27 done();28 });29 afterEach(function(done){30 stub.restore();31 clock.restore();32 done();33 });34 it('should execute the job when "start" is executed', function(done){35 scheduler = new Scheduler(mockJobWorker);36 mockJobWorker.onRun = function (){37 done();38 };39 scheduler.start();40 });41 it('should schedule a job to be executed in the future in intervals of time', function(){42 scheduler = new Scheduler(mockJobWorker);43 scheduler.start();44 clock.tick(3000);45 clock.tick(3000);46 assert.ok(stub.calledThrice);47 });48 it('should set 1 sec as the minimum interval period', function(){49 mockJobWorker.config.interval = 10; // really low interval (ms)50 scheduler = new Scheduler(mockJobWorker);51 scheduler.start();52 clock.tick(1000);53 clock.tick(1000);54 assert.ok(stub.calledThrice);55 });56 it('should set 60 sec if interval is not provided', function(){57 mockJobWorker.config.interval = null;58 scheduler = new Scheduler(mockJobWorker);59 scheduler.start();60 clock.tick(60000);61 clock.tick(60000);62 assert.ok(stub.calledThrice);63 });64 it('should allow job workers to maintain state across calls', function(){65 mockJobWorker.onRun = function (config, dependencies, cb){66 this.counter = (this.counter || 0) + 1;67 cb(null, {});68 };69 scheduler = new Scheduler(mockJobWorker);70 scheduler.start();71 clock.tick(3000);72 clock.tick(3000);73 assert.equal(mockJobWorker.counter, 3);74 });75 it('should schedule when received an empty data parameter', function(done){76 mockJobWorker.onRun = function (config, dependencies, cb){77 cb(null);78 };79 mockJobWorker.pushUpdate = function(){80 done();81 };82 scheduler = new Scheduler(mockJobWorker);83 scheduler.start();84 });85 it('should handle and log asynchronous errors', function(done){86 mockJobWorker.onRun = function (config, dependencies, cb){87 cb('error');88 };89 mockJobWorker.dependencies.logger.error = function(error){90 assert.ok(error);91 done();92 };93 scheduler = new Scheduler(mockJobWorker);94 scheduler.start();95 });96 it('should notify client on asynchronous errors', function(done){97 mockJobWorker.onRun = function (config, dependencies, cb){98 cb('error');99 };100 mockJobWorker.pushUpdate = function(data){101 assert.ok(data.error);102 done();103 };104 scheduler = new Scheduler(mockJobWorker);105 scheduler.start();106 });107 it('should allow a grace period to raise errors if retryOnErrorTimes is defined', function(){108 mockJobWorker.config.retryOnErrorTimes = 3;109 var numberJobExecutions = 0;110 var numberCallsSendDataWithErrors = 0;111 var numberCallsSendDataWithSuccess = 0;112 mockJobWorker.pushUpdate = function (data) {113 if (data.error) {114 numberCallsSendDataWithErrors++;115 } else {116 numberCallsSendDataWithSuccess++;117 }118 };119 mockJobWorker.onRun = function (config, dependencies, cb){120 if (numberJobExecutions === 0) {121 cb();122 }123 else {124 cb('err');125 }126 numberJobExecutions++;127 };128 scheduler = new Scheduler(mockJobWorker);129 scheduler.start();130 clock.tick(3000);131 clock.tick(3000/3);132 clock.tick(3000/3);133 assert.equal(numberJobExecutions, 4);134 assert.equal(numberCallsSendDataWithErrors, 1);135 });136 it('should handle synchronous errors in job execution', function(){137 mockJobWorker.onRun = sinon.stub().throws('err');138 scheduler = new Scheduler(mockJobWorker);139 scheduler.start();140 assert.ok(mockJobWorker.onRun.calledOnce);141 });142 it('should notify client when synchronous error occurred during job execution', function(done){143 mockJobWorker.onRun = sinon.stub().throws('err');144 mockJobWorker.pushUpdate = function(data){145 assert.ok(data.error);146 done();147 };148 scheduler = new Scheduler(mockJobWorker);149 scheduler.start();150 assert.ok(mockJobWorker.onRun.calledOnce);151 });152 it('should notify client on first synchronous error during job execution even when retryAttempts are configured', function(done){153 mockJobWorker.onRun = sinon.stub().throws('err');154 mockJobWorker.config.retryOnErrorTimes = 3;155 mockJobWorker.pushUpdate = function(data){156 assert.ok(data.error);157 done();158 };159 scheduler = new Scheduler(mockJobWorker);160 scheduler.start();161 assert.ok(mockJobWorker.onRun.calledOnce);162 });163 it('should schedule onRun even if there was a synchronous error', function () {164 mockJobWorker.onRun = sinon.stub().throws('err');165 scheduler = new Scheduler(mockJobWorker);166 scheduler.start();167 clock.tick(3000);168 // we expect the initial call plus one call every second (one third of the original interval in recovery mode)169 assert.equal(mockJobWorker.onRun.callCount, 4);170 });171 it('should not schedule more than one job when job execution takes long time', function() {172 mockJobWorker.onRun = function() {};173 var stub = sinon.stub(mockJobWorker, "onRun", function (config, dependencies, cb) {174 setTimeout(function() {175 cb(null, {});176 }, 10000);177 });178 scheduler = new Scheduler(mockJobWorker, {});179 scheduler.start();180 clock.tick(13000);181 clock.tick(13000);182 assert.ok(stub.calledThrice);183 });184 it('should warn if multiple job callbacks are executed', function(done) {185 mockJobWorker.onRun = function(config, dependencies, job_callback){186 job_callback(null, {});187 job_callback(null, {});188 };189 mockJobWorker.dependencies.logger.warn = function (msg) {190 assert.ok(msg.indexOf('job_callback executed more than once') > -1);191 done();192 };193 scheduler = new Scheduler(mockJobWorker);194 scheduler.start();195 });...

Full Screen

Full Screen

AsyncTask.js

Source:AsyncTask.js Github

copy

Full Screen

1define([2 "underscore",3 "constants",4 "utils",5 "eventMgr",6], function(_, constants, utils, eventMgr) {7 8 var taskQueue = [];9 10 function AsyncTask(force) {11 this.finished = false;12 this.timeout = constants.ASYNC_TASK_DEFAULT_TIMEOUT;13 this.retryCounter = 0;14 this.runCallbacks = [];15 this.successCallbacks = [];16 this.errorCallbacks = [];17 this.force = force;18 }19 20 /**21 * onRun callbacks are called by chain(). These callbacks have to call22 * chain() themselves to chain with next onRun callback or error() to23 * throw an exception or retry() to restart the task.24 */25 AsyncTask.prototype.onRun = function(callback) {26 this.runCallbacks.push(callback);27 };28 29 /**30 * onSuccess callbacks are called when every onRun callbacks have31 * succeed.32 */33 AsyncTask.prototype.onSuccess = function(callback) {34 this.successCallbacks.push(callback);35 };36 37 /**38 * onError callbacks are called when error() is called in a onRun39 * callback.40 */41 AsyncTask.prototype.onError = function(callback) {42 this.errorCallbacks.push(callback);43 };44 45 /**46 * chain() calls the next onRun callback or the onSuccess callbacks when47 * finished. The optional callback parameter can be used to pass an48 * onRun callback during execution, bypassing the onRun queue.49 */50 var currentTaskStartTime = 0;51 AsyncTask.prototype.chain = function(callback) {52 currentTaskStartTime = utils.currentTime;53 utils.logStackTrace();54 if(this.finished === true) {55 return;56 }57 // If first execution58 if(this.queue === undefined) {59 // Create a copy of the onRun callbacks60 this.queue = this.runCallbacks.slice();61 }62 // If a callback is passed as a parameter63 if(callback !== undefined) {64 callback();65 return;66 }67 // If all callbacks have been run68 if(this.queue.length === 0) {69 // Run the onSuccess callbacks70 runSafe(this, this.successCallbacks);71 return;72 }73 // Run the next callback74 var runCallback = this.queue.shift();75 runCallback();76 };77 78 /**79 * error() calls the onError callbacks passing the error parameter and80 * ends the task by throwing an exception.81 */82 AsyncTask.prototype.error = function(error) {83 utils.logStackTrace();84 if(this.finished === true) {85 return;86 }87 error = error || new Error("Unknown error");88 if(error.message) {89 eventMgr.onError(error);90 }91 runSafe(this, this.errorCallbacks, error);92 // Exit the current call stack93 throw error;94 };95 96 /**97 * retry() can be called in an onRun callback to restart the task98 */99 var currentTaskRunning = false;100 AsyncTask.prototype.retry = function(error, maxRetryCounter) {101 if(this.finished === true) {102 return;103 }104 maxRetryCounter = maxRetryCounter || 5;105 this.queue = undefined;106 if(this.retryCounter >= maxRetryCounter) {107 this.error(error);108 return;109 }110 // Implement an exponential backoff111 var delay = Math.pow(2, this.retryCounter++) * 1000;112 currentTaskStartTime = utils.currentTime + delay;113 currentTaskRunning = false;114 runTask();115 };116 /**117 * enqueue() has to be called to add the task to the task queue118 */119 AsyncTask.prototype.enqueue = function() {120 taskQueue.push(this);121 runTask();122 };123 var asyncRunning = false;124 var currentTask;125 // Determine if user is real by listening to his activity126 var isUserReal = false;127 eventMgr.addListener("onUserActive", function() {128 isUserReal = true;129 });130 131 // Run the next task in the queue if any and no other running132 function runTask() {133 134 // If there is a task currently running135 if(currentTaskRunning === true) {136 // If the current task takes too long137 if(currentTaskStartTime + currentTask.timeout < utils.currentTime) {138 currentTask.error(new Error("A timeout occurred."));139 }140 return;141 }142 if(currentTask === undefined) {143 // If no task in the queue or user has never interacted144 if(taskQueue.length === 0 || (!taskQueue[0].force && isUserReal === false)) {145 return;146 }147 // Dequeue an enqueued task148 currentTask = taskQueue.shift();149 currentTaskStartTime = utils.currentTime;150 if(asyncRunning === false) {151 asyncRunning = true;152 eventMgr.onAsyncRunning(true);153 }154 }155 // Run the task156 if(currentTaskStartTime <= utils.currentTime) {157 currentTaskRunning = true;158 currentTask.chain();159 }160 }161 162 // Call runTask periodically163 eventMgr.addListener("onPeriodicRun", runTask);164 function runSafe(task, callbacks, param) {165 try {166 _.each(callbacks, function(callback) {167 callback(param);168 });169 }170 finally {171 task.finished = true;172 if(currentTask === task) {173 currentTask = undefined;174 currentTaskRunning = false;175 }176 if(taskQueue.length === 0) {177 asyncRunning = false;178 eventMgr.onAsyncRunning(false);179 }180 else {181 runTask();182 }183 }184 }185 return AsyncTask;...

Full Screen

Full Screen

play.js

Source:play.js Github

copy

Full Screen

...40 function onKill() {41 if (running) running.Kill();42 if (window.notesEnabled) updatePlayStorage('onKill', index);43 }44 function onRun(e) {45 var sk = e.shiftKey || localStorage.getItem('play-shiftKey') === 'true';46 if (running) running.Kill();47 output.style.display = 'block';48 outpre.textContent = '';49 run1.style.display = 'none';50 var options = { Race: sk };51 running = transport.Run(text(code), PlaygroundOutput(outpre), options);52 if (window.notesEnabled) updatePlayStorage('onRun', index, e);53 }54 function onClose() {55 if (running) running.Kill();56 output.style.display = 'none';57 run1.style.display = 'inline-block';58 if (window.notesEnabled) updatePlayStorage('onClose', index);...

Full Screen

Full Screen

CAjax.js

Source:CAjax.js Github

copy

Full Screen

1var Class = function(sql){2 this.sql = sql;3 this.run = function(req, res){4 if(req.url.split("/")[1] != "ajax"){5 this.onRun(req, res);6 return;7 }8 var request = req, respons = res;9 var self = this;10 if(req.GET["method"] == "list"){11 this.sql.getListResult(function(item){12 if( !item ){13 self.onRun.call(self, request, respons);14 return;15 }16 if( item.date == request.GET["timestamp"] ){17 self.onRun.call(self, request, respons);18 return;19 }20 if(req.GET["pointType"] != 16 && req.GET["pointType"] != 100)21 req.GET["pointType"] = 5;22 var retItem = {}23 retItem.date = item.date;24 retItem.data = Array();25 for(i in item.data){26 retItem.data.push({name: item.data[i].name, date: item.data[i].date, point: item.data[i].point[req.GET["pointType"]]});27 }28 self.onRun.call(self, request, respons, retItem);29 });30 return;31 }32 if(req.GET["method"] == "statistic"){33 this.sql.getStatisticsData(function(item){34 if( !item ){35 self.onRun.call(self, request, respons);36 return;37 }38 if( item.date == request.GET["timestamp"] ){39 self.onRun.call(self, request, respons);40 return;41 }42 if(req.GET["pointType"] != 16 && req.GET["pointType"] != 100)43 req.GET["pointType"] = 5;44 var retItem = {}45 retItem.date = item.date;46 retItem.data = {};47 retItem.data.theme = item.data.theme;48 retItem.data.point = item.data.point[req.GET["pointType"]];49 self.onRun.call(self, request, respons, retItem);50 });51 return;52 }53 this.onRun(req, res);54 }55 this.onRun = function(req, res, data){56 var content = "";57 if(data)58 content = JSON.stringify(data);59 res.writeHead(200,{"Content-Type": "text/html"});60 res.write(content);61 res.end();62 }63}...

Full Screen

Full Screen

CAPI.js

Source:CAPI.js Github

copy

Full Screen

...9 }10 this.run = function(req, res){11 var self = this; 12 if(req.url.split("/")[1] != "api"){13 this.onRun(req, res, {status: 404, data: {description:"Request is not api request", code: 404}});14 return;15 }16 if(req.GET["token"] != this.token){17 this.onRun(req, res, {status: 400, data:{description:"Wrong token", code:400}});18 return;19 }20 if(req.GET["method"] == "addNewResult"){21 if(!this.checkParams(req.GET,{"name": 3, "point": 0, "theme": 0})){22 this.onRun(req, res, {status: 400, data:{description:"Wrong input parametrs", code:400}});23 return;24 }25 this.sql.addNewResultTest(req.GET["name"], req.GET["point"], req.GET["theme"], function(data, err){26 self.onRun.call(self, req, res, {status: 200, data: {description:"OK", code:200, data: data}});27 });28 return;29 }30 if(req.GET["method"] == "getListResult"){31 this.sql.getListResult(function(data){32 self.onRun.call(self, req, res, {status: 200, data: {description:"OK", code:200, data: data.data}});33 });34 return;35 }36 this.onRun(req, res, {status: 400, data: { description: "Wrong method", code: 400}});37 }38 this.onRun = function(req, res, data){39 var status = 200;40 var body = "";41 if(data && data.status)42 status = data.status;43 if(data && data.data)44 body = JSON.stringify(data.data);45 res.writeHead(status, {"Content-Type": "text/html"});46 res.write(body);47 res.end();48 }49};50module.exports = Class;

Full Screen

Full Screen

RunDropdown.js

Source:RunDropdown.js Github

copy

Full Screen

...12 <div style={styles.buttons.run.dropdownContainer}>13 <Button14 style={styles.buttons.run.dropdownButtons}15 onClick={() => {16 this.props.onRun(this.props.source);17 this.props.collapse();18 }}19 >20 Run21 </Button>22 <Button23 style={styles.buttons.run.dropdownButtons}24 onClick={() => {25 this.props.onRun(this.props.source);26 this.props.collapse();27 }}28 >29 Typecheck & Run30 </Button>31 </div>32 );33 }34 return null;35 }36}37RunDropdown.propTypes = {38 expanded: React.PropTypes.bool,39 onRun: React.PropTypes.func,40 source: React.PropTypes.string,41 collapse: React.PropTypes.func,42};43export default connect(44 state => ({45 expanded: isRunDropdownExpanded(state),46 source: getSource(state),47 }),48 dispatch => ({49 onRun(src) {50 dispatch(clearState());51 dispatch(run(src));52 },53 collapse() {54 dispatch(collapseRunDropdown());55 }...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1describe('My First Test', function() {2 it('Does not do much!', function() {3 cy.contains('type').click()4 cy.url().should('include', '/commands/actions')5 cy.get('.action-email')6 .type('

Full Screen

Using AI Code Generation

copy

Full Screen

1on('before:browser:launch', (browser = {}, args) => {2 if (browser.name === 'chrome' && browser.isHeadless) {3 args.push('--disable-dev-shm-usage')4 }5 })6{7}8{9 "env": {10 }11}

Full Screen

Using AI Code Generation

copy

Full Screen

1describe('My First Test', () => {2 it('Does not do much!', () => {3 expect(true).to.equal(true)4 })5})6describe('My First Test', () => {7 it('Does not do much!', () => {8 expect(true).to.equal(true)9 })10})11describe('My First Test', () => {12 it('Does not do much!', () => {13 expect(true).to.equal(true)14 })15})16describe('My First Test', () => {17 it('Does not do much!', () => {18 expect(true).to.equal(true)19 })20})21describe('My First Test', () => {22 it('Does not do much!', () => {23 expect(true).to.equal(true)24 })25})26describe('My First Test', () => {27 it('Does not do much!', () => {28 expect(true).to.equal(true)29 })30})31describe('My First Test', () => {32 it('Does not do much!', () => {33 expect(true).to.equal(true)34 })35})36describe('My First Test', () => {37 it('Does not do much!', () => {38 expect(true).to.equal(true)39 })40})41describe('My First Test', () => {42 it('Does not do much!', () => {43 expect(true).to.equal(true)44 })45})46describe('My First Test', () => {47 it('Does not do much!', () => {48 expect(true).to.equal(true)49 })50})51describe('My First Test', () => {52 it('Does not do much!', () => {53 expect(true).to.equal(true)54 })55})56describe('My First Test', () => {57 it('Does not do much!', ()

Full Screen

Using AI Code Generation

copy

Full Screen

1describe('My First Test', function() {2 it('Does not do much!', function() {3 expect(true).to.equal(true)4 })5})6Cypress.on('test:after:run', (test, runnable) => {7 if (test.state === 'failed') {8 const screenshot = `${Cypress.config('screenshotsFolder')}/${Cypress.spec.name}/${runnable.parent.title} -- ${test.title} (failed).png`9 addContext({ test }, screenshot)10 }11})12Cypress.on('test:after:run', (test, runnable) => {13 if (test.state === 'failed') {14 const video = `${Cypress.config('videosFolder')}/${Cypress.spec.name}.mp4`15 addContext({ test }, video)16 }17})18Cypress.on('test:after:run', (test, runnable) => {19 if (test.state === 'failed') {20 const video = `${Cypress.config('videosFolder')}/${Cypress.spec.name}.mp4`21 const screenshot = `${Cypress.config('screenshotsFolder')}/${Cypress.spec.name}/${runnable.parent.title} -- ${test.title} (failed).png`22 addContext({ test }, video)23 addContext({ test }, screenshot)24 }25})26Cypress.on('test:after:run', (test, runnable) => {27 if (test.state === 'failed') {28 const video = `${Cypress.config('videosFolder')}/${Cypress.spec.name}.mp4`29 const screenshot = `${Cypress.config('screenshotsFolder')}/${Cypress.spec.name}/${runnable.parent.title} -- ${test.title} (failed).png`30 addContext({ test }, video)31 addContext({ test }, screenshot)32 }33 else {34 const video = `${Cypress.config('videosFolder')}/${Cypress.spec.name}.mp4`35 addContext({ test }, video)36 }37})38Cypress.on('test:after:run', (test, runnable) => {39 if (test.state === 'failed') {40 const screenshot = `${Cypress.config('screenshotsFolder')}/${Cypress.spec.name}/${runnable.parent.title}

Full Screen

Using AI Code Generation

copy

Full Screen

1describe('My First Test', function() {2 it('Does not do much!', function() {3 expect(true).to.equal(true)4 })5})6{7}8{9 "scripts": {10 },11 "devDependencies": {12 }13}

Full Screen

Using AI Code Generation

copy

Full Screen

1describe('My First Test', function() {2 it('Does not do much!', function() {3 expect(true).to.equal(true)4 })5})6{7 "testFiles": "**/*.{feature,features}",8 "env": {9 }10}11describe('My First Test', function() {12 it('Does not do much!', function() {13 expect(true).to.equal(true)14 })15})16module.exports = (on, config) => {17 if(cypress_run) {18 config.testFiles = `**/${cypress_run}.*`19 }20}21Given('I visit {string}', (url) => {22 cy.visit(url)23})24Then('I see {string}', (text) => {25 cy.contains(text)26})27Given('I visit {string}', (url) => {28 cy.visit(url)29})30Then('I see {string}', (text) => {31 cy.contains(text)32})33Given('I visit {string}', (url) => {34 cy.visit(url)35})36Then('I see {string}', (text) => {37 cy.contains(text)38})

Full Screen

Using AI Code Generation

copy

Full Screen

1Cypress.Commands.add('onRun', () => {2 console.log('onRun')3})4describe('onRun', () => {5 it('runs', () => {6 cy.onRun()7 })8})9describe('onRun', () => {10 beforeEach(() => {11 cy.onRun()12 })13 it('runs', () => {14 cy.onRun()15 })16})17describe('onRun', () => {18 before(() => {19 cy.onRun()20 })21 it('runs', () => {22 cy.onRun()23 })24})25describe('onRun', () => {26 before(() => {27 cy.onRun()28 })29 beforeEach(() => {30 cy.onRun()31 })32 it('runs', () => {33 cy.onRun()34 })35})36describe('onRun', () => {37 before(() => {38 cy.onRun()39 })40 beforeEach(() => {41 cy.onRun()42 })43 afterEach(() => {44 cy.onRun()45 })46 it('runs', () => {47 cy.onRun()48 })49})50describe('onRun', () => {51 before(() => {52 cy.onRun()53 })54 beforeEach(() => {55 cy.onRun()56 })57 afterEach(() => {58 cy.onRun()59 })60 it('runs', () => {61 cy.onRun()62 })63})

Full Screen

Cypress Tutorial

Cypress is a renowned Javascript-based open-source, easy-to-use end-to-end testing framework primarily used for testing web applications. Cypress is a relatively new player in the automation testing space and has been gaining much traction lately, as evidenced by the number of Forks (2.7K) and Stars (42.1K) for the project. LambdaTest’s Cypress Tutorial covers step-by-step guides that will help you learn from the basics till you run automation tests on LambdaTest.

Chapters:

  1. What is Cypress? -
  2. Why Cypress? - Learn why Cypress might be a good choice for testing your web applications.
  3. Features of Cypress Testing - Learn about features that make Cypress a powerful and flexible tool for testing web applications.
  4. Cypress Drawbacks - Although Cypress has many strengths, it has a few limitations that you should be aware of.
  5. Cypress Architecture - Learn more about Cypress architecture and how it is designed to be run directly in the browser, i.e., it does not have any additional servers.
  6. Browsers Supported by Cypress - Cypress is built on top of the Electron browser, supporting all modern web browsers. Learn browsers that support Cypress.
  7. Selenium vs Cypress: A Detailed Comparison - Compare and explore some key differences in terms of their design and features.
  8. Cypress Learning: Best Practices - Take a deep dive into some of the best practices you should use to avoid anti-patterns in your automation tests.
  9. How To Run Cypress Tests on LambdaTest? - Set up a LambdaTest account, and now you are all set to learn how to run Cypress tests.

Certification

You can elevate your expertise with end-to-end testing using the Cypress automation framework and stay one step ahead in your career by earning a Cypress certification. Check out our Cypress 101 Certification.

YouTube

Watch this 3 hours of complete tutorial to learn the basics of Cypress and various Cypress commands with the Cypress testing at LambdaTest.

Run Cypress automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful