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