How to use this.setupServer method in Cypress

Best JavaScript code snippet using cypress

index.js

Source:index.js Github

copy

Full Screen

1'use strict'; /* globals describe, it, beforeEach, afterEach, before, after, __dirname, module, global */2const chai = require('chai');3chai.use(require('chai-as-promised'));4chai.should();5global.expect = chai.expect;6const {7	concurrent: { _async, asyncClass, spawn, promisify, },8	fs: { FS, Path, },9} = require('es6lib');10const HttpServer = require('../server/index.js');11const buildExt = require('../../build.js');12const makeTempDir = promisify(require('temp').track().mkdir);13const eventToPromise = require('event-to-promise');14const Https = require('https'), Http = require('http');15const getBody = require('raw-body');16const { Builder, } = require('selenium-webdriver');17const Chrome = require('selenium-webdriver/chrome');18const Firefox = require('selenium-webdriver/firefox');19const { Key, } = require('selenium-webdriver/lib/input');20const Test = asyncClass({21	constructor: function*({ noExt = false, server, } = { }) {22		if (!noExt) {23			// start a web server which the extension will contact during startup24			this.setupServer = new Http.Server(this._onSetupRequest.bind(this));25			(yield eventToPromise(this.setupServer.listen(0), 'listening'));26			this.setupServer.close = promisify(this.setupServer.close);27			this.setupPort = this.setupServer.address().port;28			// build add-on and create a firefox builder with it29			this.tempDir = (yield makeTempDir('add-on-build'));30			this.extName = (yield buildExt({31				icons: false, tld: false, // no need to rebuild these every time32				selenium: { setupPort: this.setupPort, },33				xpi: true,34				outDir: this.tempDir,35			}));36			this.extPath = Path.join(this.tempDir, this.extName +'.xpi');37		}38		this._makeBuilder();39		const log = this.serverLogs = [ ];40		this.server = (yield new HttpServer(Object.assign({41			httpPorts: [ 0, ],42			httpsPorts: [ 0, ],43			upgradePorts: { },44			log(header) { log.push(header); },45			serveFromFS: false,46			favicon: 'ignore',47		}, server)));48		this.pendingStartUp = null; // holds a PromiseCapability with additional options while the browser is starting49		this.browser = null; // reference to the browser driver, while it runs50		return this;51	},52	// start browser with config, wait for the add-on to have started, clear server logs53	start: function*({ storage, }) {54		if (this.browser || this.pendingStartUp) { throw new Error('Browser already started'); }55		const ctx = this.pendingStartUp = { options: { storage, token: Math.random().toString(36).slice(2), }, };56		const done = ctx.promise = this.setupServer ? new Promise((y, n) => { ctx.resolve = y; ctx.reject = n; }) : Promise.resolve();57		this.browser = (yield this.builder.buildAsync());58		(yield done);59		this.pendingStartUp = null;60		this.takeLogs();61		return this.browser;62	},63	// close browser, clear server logs64	stop: function*() { // TODO: add 'force' option65		this.pendingStartUp && (yield this.pendingStartUp.promise);66		this.browser && (yield this.browser.quit());67		this.browser = null;68		this.takeLogs();69	},70	// stop servers, calls .stop(true)71	destroy: function*() {72		(yield this.stop(true));73		this.server && (yield this.server.close());74		this.server = null;75		this.setupServer && (yield this.setupServer.close());76		this.setupServer = null;77		// TODO: destroy builder78		this.builder = null;79	},80	takeLogs() {81		return this.serverLogs.splice(0, Infinity);82	},83	peekLogs() {84		return this.serverLogs.slice(0, Infinity);85	},86/*87	openTab: function*(url) {88		(yield this.browser.executeScript('window.open()')); console.log('opened');89		// TODO: wait?90		(yield this.focusTab()); console.log('focused');91		url != null && (yield this.browser.get(url)); console.log('navigated');92	},93	focusTab: function*(index) {94		const tabs = (yield this.browser.getAllWindowHandles()); console.log('tabs', tabs);95		index = index == null ? tabs.length - 1 : index < 0 ? tabs.length - 1 + length : length;96		index = Math.max(0, Math.min(index, tabs.length - 1)); console.log('index', index);97		(yield this.browser.switchTo().window(tabs[index]));98	},99	closeTab: function*(index) {100		index != null && (yield this.focusTab(index));101		(yield this.browser.close());102		// (yield this.browser.executeScript('window.close()'));103		(yield this.focusTab(0));104	},105*/106	_makeBuilder: function() {107		const chromeOpts = new Chrome.Options();108		chromeOpts.addArguments(`load-extension=${ this.tempDir }/webextension`);109		const ffProfile = new Firefox.Profile();110		this.extPath && ffProfile.addExtension(this.extPath);111		ffProfile.setPreference('extensions.@stop-fingerprinting.sdk.console.logLevel', 'all');112		ffProfile.setPreference('xpinstall.signatures.required', false); // allow unsigned add-on (requires alpha/devEdition/unbranded build)113		ffProfile.setPreference('extensions.checkCompatibility.51.0a', false); // FF51 devEdition114		ffProfile.setPreference('extensions.checkCompatibility.51.0b', false); // FF51 beta115		// disable all caching, for now this is an acceptable way to handle caching in these tests,116		// but it needs to be removed once this extension affects caching itself117		ffProfile.setPreference('browser.cache.disk.enable', false);118		ffProfile.setPreference('browser.cache.memory.enable', false);119		ffProfile.setPreference('browser.cache.offline.enable', false);120		ffProfile.setPreference('network.http.use-cache', false);121		// enable all debugging output within Firefox122		ffProfile.setPreference('extensions.@stop-fingerprinting.sdk.console.logLevel', 'all');123		// these don't seem to work124		ffProfile.setAcceptUntrustedCerts(true);125		ffProfile.setAssumeUntrustedCertIssuer(true);126		const ffOpts = new Firefox.Options();127		// ffOpts.setBinary(new Firefox.Binary().useDevEdition(true));128		ffOpts.setBinary(String.raw`C:\Program Files\Firefox Developer Edition\firefox.exe`);129		ffOpts.setProfile(ffProfile);130		this.builder = new Builder()131		.forBrowser('firefox')132		// .setChromeOptions(chromeOpts)133		.setFirefoxOptions(ffOpts)134		.disableEnvironmentOverrides()135		;136	},137	_onSetupRequest({ url, body, }, out) {138		const ctx = this.pendingStartUp;139		if (!ctx) {140			console.error('Unexpected startup request:', url);141			out.writeHead(404);142			out.end(); return;143		}144		switch (url.slice(1)) {145			case 'get-options': {146				if (ctx.options) {147					out.write(JSON.stringify(ctx.options));148				} else {149					out.writeHead(404);150				}151				ctx.options = null;152			} break;153			case 'statup-done': {154				ctx.resolve();155			} break;156			case 'statup-failed': {157				getBody(arguments[0])158				.catch(() => '<unknown>')159				.then(error => ctx.reject(new Error('Failed to start extension: '+ error)));160			} break;161			default: {162				console.error('request to unknown path:', url);163				out.writeHead(404);164			}165		}166		out.end();167	},168	[Symbol.toStringTag]: 'Test',169});170Test.register = function(options, done) {171	let test, getTest = new Test(options), port;172	before(_async(function*() {173		this.timeout(6000);174		test = (yield getTest);175		port = test.server.http[test.server.http.length - 1].address().port;176		(yield done(test));177	}));178	beforeEach(_async(function*() {179		test.server.files = { 'reset.html': 'Hey Ho!', };180		(yield test.browser.get(`http://localhost:${ port }/reset.html`));181		test.server.files = null;182		test.takeLogs();183	}));184	afterEach(_async(function*() {185	}));186	after(_async(function*() {187		(yield test.destroy());188	}));189};...

Full Screen

Full Screen

server.js

Source:server.js Github

copy

Full Screen

...49    this._super();50    // Start up the port store.51    portStore.start();52    // Set up the server for master -- proxy (net server) if multiple ports, express if not.53    this.setupServer();54  };55  Server.prototype.setupServer = function() {56    if (this.server || (this.isMaster && this.numProcesses)) {57      return;58    }59    this._setupExpressServer();60  };61  // Slave62  // http://expressjs.com/en/api.html#req.body63  Server.prototype._setupExpressServer = function() {64    this.app = express();65    // Use a cookie parser.66    this.app.use(cookieParser(credentials.cookieSecret));67    // Use a body parser for POSTS.68    this.app.use(bodyParser.json());69    // For parsing application/x-www-form-urlencoded70    this.app.use(bodyParser.urlencoded({extend: true}));71    // Setup the session.72    this.app.use(this._createSession());73    // Setup the public routes.74    this._publicRoutes = [];75    for (var route in this.publicRoutes) {76      var serverPath = this.publicRoutes[route];77      this._publicRoutes.push(route);78      this.app.use(route, express.static(serverPath));79    }80    // Setup the endpoints.81    for (var i=0; i< this.routes.length; i++) {82      this.addEndpoint(this.routes[i]);83    }84    // Callback to call once express app is listening.85    var cb = function() { console.log('Server (', this.__className, ') has started.'); }.bind(this);86    // Have it listen to localhost87    //if (this.isMaster) {88      this.server = this.app.listen(this.port, cb);89    //} else {90      //this.server = this.app.listen(0, 'localhost', cb);91    //}92    // Setup the sockets.93    for (var i=0; i< this.sockets.length; i++) {94      this.addSocket(this.sockets[i]);95    }96  };97  Server.prototype._createSession = function() {98    var RedisStore = connectRedis(expressSession);99    return expressSession({100      'store': new RedisStore({ 'client': redisManager.getCluster() }),101      'secret': credentials.cookieSecret,102      'cookie': this._getCookieParams(),103      'resave': false,104      'saveUninitialized': false105    });106  };107  Server.prototype._getCookieParams = function() {108    // https://github.com/expressjs/session#cookie-options109    // Note: for https sites, read that.110    return {111      'path': '/',112      'httpOnly': true,113      'maxAge': 86400*90, // 90 days114      // 'domain': 'your domain so you can have multiple sub-domains using the same cookie'115      'secure': false116    }117  };118  Server.prototype.addEndpoint = function(endpointOrOptions) {119    if (!endpointOrOptions) return;120    var ep;121    if (endpointOrOptions.prototype.__templateType === 'endpoint') {122      ep = new endpointOrOptions({'app': this.app});123    } else {124      endpointOrOptions['app'] = this.app;125      ep = new Endpoint(endpointOrOptions);126    }127    this.endpoints[ep.url] = ep;128  };129  Server.prototype.addSocket = function(socket) {130    var publicRoute = this._publicRoutes[0] || '';131    var s = new socket({'server': this.server, 'publicRoute': publicRoute, 'app': this.app});132    this.sockets[socket.__id] = s;133  };134  Server.prototype.onFork = function() {135    if (this.isMaster) return;136    this._super.apply(this, arguments);137    this.setupServer();138  };139  // Listening to messages sent by master -- if it's a connection event, then140  // emulate the connection event on the server by emitting the event that master sent us.141  Server.prototype.onProcessMessage = function(message, connection) {142    console.log(message, connection);143    if (this.isMaster || message !== (config.stickySessionEvent || 'sticky-session:connection')) return;144    connection.resume();145    this.server.emit('connection', connection);146  };147  return extend(MicroService, Server);...

Full Screen

Full Screen

pyserialclient.js

Source:pyserialclient.js Github

copy

Full Screen

...7    this.pySerialServer = undefined8    this.socket = undefined9    this.socketIoConnected = false10    this.serialPortConnected = false11    this.setupServer(callbacks)12    this.setupSocketIO(callbacks)13  }14  setupServer(callbacks) {15    let options = {16      mode: 'text',17      pythonOptions: ['-u'],18      scriptPath: path.join(__dirname, 'python')19    }20    if (this.pySerialServer) {21      delete this.pySerialServer22    }23    this.pySerialServer = new PythonShell('pyserialserver.py', options)24    this.pySerialServer.on('message', (message) => {25      console.log("pyserialserver.py - stdout:", message)26    })27    this.pySerialServer.end( (err) => {28      console.log('pyserialserver.py finished')29      // If we didn't receive a disconnect event from socketio, we know30      // the python script crashed.  We will attempt to restart it31      if (this.socketIoConnected === true) {32        console.log(err)33        console.log("Ungraceful Disconnection, restarting pyserialserver")34        this.socketIoConnected = false35        this.serialPortConnected = false36        callbacks.onServerError.call(callbacks.obj)37        this.setupServer(callbacks)38      }39    })40  }41  setupSocketIO(callbacks) {42    this.socket = io.connect('http://localhost:8000', {reconnect: true,43    transports: ['websocket']} )44    // Socket.io built in event listneres45    this.socket.on('connect', () => {46      console.log("Socket IO connected.")47      this.socketIoConnected = true48      callbacks.onServerConnected.call(callbacks.obj)49      this.getPortList()50    })51    this.socket.on('disconnect', () => {...

Full Screen

Full Screen

SocketService.js

Source:SocketService.js Github

copy

Full Screen

...65  ],66  methods: [67    function init() {68      if ( ! this.listen ) return;69      this.setupServer(this.port);70    },71    function setupServer(port) {72      var server = this.server = new require('net').Server();73      this.server.on('connection', this.onConnection);74      this.server.on('error', function(error) {75        this.error('foam.net.node.SocketService: Server error', error);76        server.unref();77        if ( error.code === 'EADDRINUSE' ) {78          var port = Math.floor( 10000 + ( Math.random() * 10000 ) );79          this.info('foam.net.node.SocketService: Retrying on port', port);80          this.setupServer(port);81        }82      }.bind(this));83      if ( this.listen ) {84        this.server.on('listening', function() {85          this.listening = true;86        }.bind(this));87        this.server.listen(this.port = port);88      }89    },90    function addSocket(socket) {91      var socketBox = this.RawSocketBox.create({92        socket: socket93      })94      var X = this.creationContext.createSubContext({...

Full Screen

Full Screen

instrumentation-middleware.js

Source:instrumentation-middleware.js Github

copy

Full Screen

1'use strict';2const pino = require('pino')();3const cpuProfiler = require('./v8-cpu-profiler');4const heapProfiler = require('heap-profile');5heapProfiler.start();6class InstrumentationMiddleware {7  constructor(app) {8    this.app = app;9    this.app.addHook('preHandler', async (request, reply) => {10      const route = request.req.url.replace(new RegExp(/\//, 'g'), "");11      if (this.isCpuProfiling(request.req.url))12        cpuProfiler.startProfiling();13      if (this.isMemoryProfiling(request.req.url))14        heapProfiler.write(`./out/${route}-${request.id}-before.heapprofile`);15      if (this.isLogging(request.req.url))16        pino.info(request.req);17    });18    this.app.addHook('onSend', async (request, reply, payload) => {19      const route = request.req.url.replace(new RegExp(/\//, 'g'), "");20      if (this.isLogging(request.req.url))21        pino.info(reply.res);22      if (this.isMemoryProfiling(request.req.url))23        heapProfiler.write(`./out/${route}-${request.id}-after.heapprofile`);24      if (this.isCpuProfiling(request.req.url))25        cpuProfiler.stopProfiling(`./out/${route}-${request.id}.cpuprofile`);26    });27    this.app.addHook('onRoute', (routeOptions) => {28      if (routeOptions.url in this.routes) return;29      this.routes.push(routeOptions.url);30    })31    this.routes = []32    this.instrumenting = {};33    this.setupServer = require('express')();34    this.setupServer.use(require('express').json());35    this.setupServer.get('/', (req, res) => {36      res.json(this.routes.map((route) => {37        return {38          path: route,39          instrumented: this.instrumenting[route] || [],40          instrumentations: this.availableInstrumentations(),41        }42      }));43    });44    this.setupServer.post('/', (req, res) => {45      this.instrumenting = req.body;46      res.send();47    });48    this.setupServer.listen('/tmp/instrumentation.sock')49  }50  isCpuProfiling(route) {51    return this.isInstrumenting(route, 'cpu-profiler');52  }53  isMemoryProfiling(route) {54    return this.isInstrumenting(route, 'memory-profiler');55  }56  isLogging(route) {57    return this.isInstrumenting(route, 'logging');58  }59  isInstrumenting(route, instrumentation) {60    return (this.instrumenting[route] || []).includes(instrumentation);61  }62  availableInstrumentations() {63    return [64      'cpu-profiler',65      'memory-profiler',66      'logging',67    ];68  }69}70module.exports = {71  InstrumentationMiddleware...

Full Screen

Full Screen

Application.js

Source:Application.js Github

copy

Full Screen

1"use strict";2Object.defineProperty(exports, "__esModule", { value: true });3const express = require("express");4const mongoose_1 = require("mongoose");5class Application {6    constructor(opt) {7        this.app = express();8        this.port = opt.port;9        this.db_address = opt.db_address;10        this.middllewaresArray = opt.middllewaresArray;11        this.setUpServer();12        this.setDb();13        this.setUpMiddleWares();14    }15    setUpServer() {16        this.app.listen(this.port, () => {17            console.log("App is listening to port " + this.port);18        });19    }20    setDb() {21        this.db_address && (mongoose_1.default.connect(this.db_address, {22            useNewUrlParser: true, useUnifiedTopology: true23        })24            .then(_ => {25            console.log("Db connected");26        })27            .catch(err => {28            throw err;29        }));30    }31    setUpMiddleWares() {32        if (this.middllewaresArray) {33            this.middllewaresArray.map(m => {34                m.path ? this.app.use(m.path, m.cb) : this.app.use(m.cb);35            });36        }37    }38}...

Full Screen

Full Screen

Room.js

Source:Room.js Github

copy

Full Screen

...5class Room extends Component {6  constructor(props) {7    super(props);8    this.handleClick = this.handleClick.bind(this);9    this.setupServer();10  }11  /**12   * Tirggered when the user selects a room13   */14  handleClick = () => {15    if (16      !this.props.room ||17      (this.props.room !== undefined && this.props.room !== this.props.roomId)18    )19      this.props.socket.emit("updateRoom", this.props.roomId);20  };21  /**22   * Setup the real time server23   */...

Full Screen

Full Screen

setup.js

Source:setup.js Github

copy

Full Screen

1(function() {2	var app = angular.module('rcSetup', []);3	app.controller('rcSetupController', function() {4		this.setupServer = function() {5			// ....6		};7	});8	9	var flower = []...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1import { setupServer } from 'msw/node'2import { handlers } from './handlers'3const server = setupServer(...handlers)4beforeAll(() => server.listen())5afterEach(() => server.resetHandlers())6afterAll(() => server.close())7describe('test', () => {8  it('test', () => {9  })10})11import { rest } from 'msw'12import { setupServer } from 'msw/node'13  rest.get('/test', (req, res, ctx) => {14    return res(15      ctx.json({16      })17  }),18{19}

Full Screen

Using AI Code Generation

copy

Full Screen

1import { setupServer } from 'msw/node'2import { handlers } from './handlers'3export const server = setupServer(...handlers)4before(() => {5  server.listen()6})7after(() => {8  server.close()9})10import { rest } from 'msw'11  rest.get('/greeting', (req, res, ctx) => {12    return res(13      ctx.json({14      }),15  }),16describe('Example', () => {17  it('works', () => {18    cy.request('/greeting').should((response) => {19      expect(response.body.greeting).to.eq('hello there')20    })21  })22})

Full Screen

Using AI Code Generation

copy

Full Screen

1import './commands'2import './commands'3import './commands'4import './commands'5import './commands'6import './commands'7import './commands'8import './commands'9import './commands'10import './commands'11import './commands'12import './commands'13import './commands'14import './commands'15import './commands'16import './commands'17import './commands'18import './commands'

Full Screen

Using AI Code Generation

copy

Full Screen

1import { setupServer } from 'msw/node'2import { handlers } from './handlers'3export const server = setupServer(...handlers)4beforeEach(() => {5  server.listen()6})7afterEach(() => {8  server.resetHandlers()9})10afterAll(() => {11  server.close()12})13import { server } from './test'14Cypress.Commands.add('login', (overrides = {}) => {15  const user = {

Full Screen

Using AI Code Generation

copy

Full Screen

1import {setupServer} from 'cypress-server-mock';2import {mockServer} from 'cypress-server-mock';3const mockServer = require('cypress-server-mock');4const setupServer = require('cypress-server-mock');5const mockServer = require('cypress-server-mock').mockServer;6const setupServer = require('cypress-server-mock').setupServer;7const mockServer = require('cypress-server-mock').mockServer;8const setupServer = require('cypress-server-mock').setupServer;9const mockServer = require('cypress-server-mock').mockServer;10const setupServer = require('cypress-server-mock').setupServer;11const mockServer = require('cypress-server-mock').mockServer;12const setupServer = require('cypress-server-mock').setupServer;13const mockServer = require('cypress-server-mock').mockServer;14const setupServer = require('cypress-server-mock').setupServer;15const mockServer = require('cypress-server-mock').mockServer;16const setupServer = require('cypress-server-mock').setupServer;17const mockServer = require('cypress-server-mock').mockServer;18const setupServer = require('cypress-server-mock').setupServer;19const mockServer = require('cypress-server-mock').mockServer;20const setupServer = require('cypress-server-mock').setupServer;

Full Screen

Using AI Code Generation

copy

Full Screen

1describe("My First Test", function() {2  it("Visits the Kitchen Sink", 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

1import { setupServer } from 'msw/node'2import { rest } from 'msw'3  { id: 1, text: 'todo 1', completed: false },4  { id: 2, text: 'todo 2', completed: false },5const server = setupServer(6  rest.get('/api/todos', (req, res, ctx) => {7    return res(ctx.json(todos))8  }),9  rest.post('/api/todos', (req, res, ctx) => {10    todos.push(todo)11    return res(ctx.status(201), ctx.json(todo))12  })13beforeAll(() => server.listen())14afterEach(() => server.resetHandlers())15afterAll(() => server.close())16it('should fetch todos', () => {17  cy.request('/api/todos').should((response) => {18    expect(response.body).to.deep.equal(todos)19  })20})21it('should create a todo', () => {22  const todo = { text: 'todo 3', completed: false }23  cy.request('POST', '/api/todos', todo).should((response) => {24    expect(response.status).to.equal(201)25    expect(response.body).to.deep.equal({26    })27  })28  cy.request('/api/todos').should((response) => {29    expect(response.body).to.deep.equal([...todos, todo])30  })31})

Full Screen

Using AI Code Generation

copy

Full Screen

1const PORT = 3000;2describe("My First Test", function() {3  beforeEach(function() {4    this.setupServer();5  });6  it("Visits the app root url", function() {7    cy.visit("/");8    cy.title().should("include", "Welcome to Your Vue.js App");9  });10});11describe("My Second Test", function() {12  it("Visits the app root url", function() {13    cy.visit("/");

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