├── API-transaction-scripts ├── README.md ├── TE-API-Basic-auth.js ├── TE-API-Bearer-auth.js ├── client-certificate.js ├── custom-settings-proxy.js ├── custom-ssl-certificate.js ├── disable-ssl-verification.js ├── proxy+custom-ssl-certificate.js ├── proxy+disable-ssl-verification.js ├── simple-net-send-recv.js ├── simple-tls-send-recv.js └── test-settings-proxy.js ├── LICENSE ├── README.md ├── applications ├── README.md ├── office-365 │ ├── README.md │ ├── excel-login.js │ ├── exchange-ews-list-inbox.js │ ├── exchange-ews-oauth-list-inbox.js │ ├── exchange-ews-send-email.js │ ├── graphapi-mail-inbox.js │ ├── graphapi-oauth-list-inbox.js │ ├── onedrive-download.js │ ├── outlook-send-email.js │ ├── outlook-sendemail.js │ ├── powerpoint-login.js │ ├── sharepoint-login-download.js │ ├── teams-chat.js │ └── word-login.js └── salesforce │ ├── SF_lightning_case_load.js │ ├── Salesforce_Lightning_login.js │ └── placeholder_file └── examples ├── OAuthProtectedRestAPI.js ├── assertCondition.js ├── checkIfElementExists.js ├── clickSpecificPosition.js ├── closeConditionalPopup.js ├── closeRandomPopupAsync.js ├── configuringImplicitWaits.js ├── customTransactionStartTime.js ├── detectJsErrors.js ├── dismissBrowserNativeAlert.js ├── duoApiAuth.js ├── enterBasicAuthCredentials.js ├── fetchAPIWithBasicAuth.js ├── findAnyElement.js ├── html5CanvasInteractionCoordinates.js ├── httpRequestConnectProxy.js ├── iframe.js ├── imapLoginAndFetchEmail.js ├── loadPage.js ├── moveMouseIntoElement.js ├── reattemptClickUntilOtherElementExists.js ├── repeatingError.js ├── scrollElementIntoView.js ├── shadowDomFindShadowRoot.js ├── smtpServerAvailability.js ├── solveMathCaptcha.js ├── switchToNextTab.js ├── switchToTabWithUrl.js ├── takeScreenshot.js ├── timePortionOfScript.js ├── usingCredentials.js ├── usingTOTPTwoFactorAuth.js ├── waitForCondition.js ├── waitForDownload.js └── waitForUrl.js /API-transaction-scripts/README.md: -------------------------------------------------------------------------------- 1 | # ThousandEyes - API Transaction Examples 2 | These .js files are a collection of Javascript synthetic transaction examples for testing various REST API endpoints. Each of the .js scripts listed here represent a complete transaction test and can be used standalone. 3 | 4 | ## How to Use 5 | 1. Clone the repository or just download/copy an individual .js transaction script file. 6 | 2. In ThousandEyes [create a new application synthetic transaction test](https://www.thousandeyes.com/resources/getting-started-with-transactions-tutorial). 7 | 3. Paste in the contents of .js transaction script file (or select `Import from JS File`) 8 | 4. Set any other test settings and save the test. Recommended to start with 3-5 agents, Round Robin testing with 10/1 minute test intervals. 9 | 5. Create a a new credential or rename the credential name in the transaction script to use your own. Open the Transaciton test, select the credentials button and check your credential. Make sure your credential name matches what's used in the transaction. 10 | 11 | -------------------------------------------------------------------------------- /API-transaction-scripts/TE-API-Basic-auth.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes Transaction script which calls an API endpoint that requires 3 | // a Basic authenication token 4 | // 5 | import { By, Key, until } from 'selenium-webdriver'; 6 | import { driver, markers, credentials, downloads, transaction, authentication } from 'thousandeyes'; 7 | import fetch from 'node-fetch'; 8 | 9 | runScript(); 10 | 11 | async function runScript() { 12 | 13 | // update with your ThousandEyes username - typically your email address 14 | const username = 'username@example.com'; 15 | 16 | // retrieve the Basic authentication token from the credential stored as 'TE-API-Basic-token' 17 | // in the ThousandEyes Credentials Repository 18 | const token = credentials.get('TE-API-Basic-token'); 19 | 20 | // encode the token 21 | var buffer = Buffer.from(username+':'+token); 22 | var apiToken = buffer.toString('base64'); 23 | 24 | // set the method and headers in the request body 25 | var requestBody = { 26 | method: 'GET', 27 | headers: { 28 | 'Authorization': 'Basic '+apiToken 29 | } 30 | } 31 | 32 | // call the API endpoint, in this case https://developer.thousandeyes.com/v6/agents/ 33 | markers.start('FetchTime'); 34 | const response = await fetch('https://api.thousandeyes.com/v6/agents.json?agentTypes=ENTERPRISE', requestBody); 35 | const responseText = await response.text(); 36 | if (!response.ok) { 37 | await console.log(responseText); 38 | throw new Error('non-200 response'); 39 | } 40 | markers.stop('FetchTime'); 41 | 42 | // verify that a particular string of text was returned by the API call 43 | if (responseText.includes('Qwest Communications')) { 44 | await console.log('Success - found "Qwest Communications"'); 45 | } else { 46 | throw new Error('"Qwest Communications" not found'); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /API-transaction-scripts/TE-API-Bearer-auth.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes transaction script which calls an API endpoint that requires 3 | // a Bearer authenication token 4 | // 5 | import { By, Key, until } from 'selenium-webdriver'; 6 | import { driver, markers, credentials, downloads, transaction } from 'thousandeyes'; 7 | import fetch from 'node-fetch'; 8 | 9 | runScript(); 10 | 11 | async function runScript() { 12 | 13 | // retrieve the Bearer authentication token from the credential stored as 'TE-API-Bearer-token' 14 | // in the ThousandEyes Credentials Repository 15 | const apiToken = credentials.get('TE-API-Bearer-token'); 16 | 17 | // set the method and headers in the request body 18 | var requestBody = { 19 | method: 'GET', 20 | headers: { 21 | 'Authorization': 'Bearer ' + apiToken 22 | } 23 | } 24 | 25 | // call the API endpoint, in this case https://developer.thousandeyes.com/v6/agents/ 26 | markers.start('FetchTime'); 27 | const response = await fetch('https://api.thousandeyes.com/v6/agents.json?agentTypes=ENTERPRISE', requestBody); 28 | const responseText = await response.text(); 29 | if (!response.ok) { 30 | await console.log(responseText); 31 | throw new Error('non-200 response'); 32 | } 33 | markers.stop('FetchTime'); 34 | 35 | // verify that a particular string of text was returned by the API call 36 | if (responseText.includes('Qwest Communications')) { 37 | await console.log('Success - found "Qwest Communications"'); 38 | } else { 39 | throw new Error('"Qwest Communications" not found'); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /API-transaction-scripts/client-certificate.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes Transaction script which uses client certificates authentication 3 | // 4 | import fetch from 'node-fetch'; 5 | import { fetchAgent } from 'thousandeyes'; 6 | import assert from 'assert'; 7 | 8 | runCode(); 9 | 10 | async function runCode() { 11 | // X509 of the certificate 12 | // NOTE: these are fake certificates make sure to replace with real ones 13 | const key = `-----BEGIN CERTIFICATE----- 14 | self signed certificate 15 | -----END CERTIFICATE----- 16 | `; 17 | 18 | const clientCert = `-----BEGIN CERTIFICATE----- 19 | self signed certificate 20 | -----END CERTIFICATE----- 21 | `; 22 | const passphrase = ''; 23 | 24 | // SSL options with client certificate, key and passphrase 25 | const sslOptions = { key: key, cert: clientCert, passphrase: passphrase }; 26 | // get a fetch agent 27 | const agent = fetchAgent.getHttpsAgent(sslOptions); 28 | 29 | // set the agent in the request options 30 | const requestOptions = { 31 | agent: agent, 32 | }; 33 | 34 | // call the endpoint 35 | // NOTE: this is a fake endpoint make sure to replace with a real one 36 | const response = await fetch('https://some-website.com', requestOptions); 37 | 38 | //verify that response status is 200 39 | assert.equal(200, response.status); 40 | } 41 | -------------------------------------------------------------------------------- /API-transaction-scripts/custom-settings-proxy.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes Transaction script which uses proxies with custom configuration 3 | // 4 | import {markers, fetchAgent } from 'thousandeyes'; 5 | import fetch from 'node-fetch'; 6 | import assert from 'assert'; 7 | 8 | runScript(); 9 | 10 | async function runScript() { 11 | // HTTP proxy 12 | markers.start('HTTP Proxy'); 13 | // custom proxy settings 14 | const httpProxySettings = { 15 | host: 'proxy-host.com', 16 | port: 3333, 17 | proxyAuth: { 18 | username: 'username', 19 | password: 'password' 20 | } 21 | }; 22 | // get a HTTP proxy agent 23 | const httpProxyAgent = fetchAgent.getHttpProxyAgent(httpProxySettings) 24 | // set the agent in the request options 25 | const httpProxyRequestOptions = { 26 | agent: httpProxyAgent, 27 | }; 28 | // call the endpoint with a self signed certificate 29 | // NOTE: this is a fake endpoint make sure to replace with a real one 30 | const response1 = await fetch('http://some-website.com', httpProxyRequestOptions); 31 | // verify that response status is 200 32 | assert.equal(200, response1.status); 33 | markers.end('HTTP Proxy'); 34 | 35 | // HTTPS proxy 36 | markers.start('HTTPS Proxy'); 37 | // custom proxy settings 38 | const httpsProxySettings = { 39 | host: 'ssl-proxy-host.com', 40 | port: 3333, 41 | proxyAuth: { 42 | username: 'username', 43 | password: 'password' 44 | } 45 | }; 46 | // get a HTTPS proxy agent 47 | const httpsProxyAgent = fetchAgent.getHttpsProxyAgent(httpsProxySettings) 48 | // set the agent in the request options 49 | const httpsProxyRequestOptions = { 50 | agent: httpsProxyAgent, 51 | }; 52 | // call the endpoint with a self signed certificate 53 | // NOTE: this is a fake endpoint make sure to replace with a real one 54 | const response2 = await fetch('https://some-website.com', httpsProxyRequestOptions); 55 | // verify that response status is 200 56 | assert.equal(200, response2.status); 57 | markers.end('HTTPS Proxy'); 58 | 59 | // PAC proxy 60 | markers.start('PAC Proxy'); 61 | // custom proxy settings 62 | const pacProxySettings = { 63 | pacScriptUrl: 'http://pac-script-location.com', 64 | proxyAuth: { 65 | username: 'username', 66 | password: 'password' 67 | } 68 | }; 69 | // get a PAC proxy agent 70 | const pacProxyAgent = fetchAgent.getPACProxyAgent(pacProxySettings) 71 | // set the agent in the request options 72 | const pacProxyRequestOptions = { 73 | agent: pacProxyAgent, 74 | }; 75 | // call the endpoint with a self signed certificate 76 | // NOTE: this is a fake endpoint make sure to replace with a real one 77 | const response3 = await fetch('https://some-website.com', pacProxyRequestOptions); 78 | // verify that response status is 200 79 | assert.equal(200, response3.status); 80 | markers.end('PAC Proxy'); 81 | }; 82 | -------------------------------------------------------------------------------- /API-transaction-scripts/custom-ssl-certificate.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes Transaction script which trusts arbitrary certificates 3 | // 4 | import fetch from 'node-fetch'; 5 | import { fetchAgent } from 'thousandeyes'; 6 | import assert from 'assert'; 7 | 8 | runCode(); 9 | 10 | async function runCode() { 11 | // X509 of the certificate 12 | // NOTE: these are fake certificates make sure to replace with real ones 13 | const cert1 = `-----BEGIN CERTIFICATE----- 14 | self signed certificate 15 | -----END CERTIFICATE----- 16 | `; 17 | 18 | const cert2 = `-----BEGIN CERTIFICATE----- 19 | self signed certificate 20 | -----END CERTIFICATE----- 21 | `; 22 | // SSL options with custom certificates 23 | const sslOptions = { customCA: [cert1, cert2] }; 24 | // get a fetch agent with custom certificates 25 | const agent = fetchAgent.getHttpsAgent(sslOptions); 26 | 27 | // set the agent in the request options 28 | const requestOptions = { 29 | agent: agent, 30 | }; 31 | 32 | // call the endpoint with a self signed certificate 33 | // NOTE: this is a fake endpoint make sure to replace with a real one 34 | const response = await fetch('https://some-website-with-a-self-signde-cert.com', requestOptions); 35 | 36 | //verify that response status is 200 37 | assert.equal(200, response.status); 38 | } 39 | -------------------------------------------------------------------------------- /API-transaction-scripts/disable-ssl-verification.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes Transaction script which disables ssl verification 3 | // 4 | import fetch from 'node-fetch'; 5 | import { fetchAgent } from 'thousandeyes'; 6 | import assert from 'assert'; 7 | 8 | runCode(); 9 | 10 | async function runCode() { 11 | 12 | // SSL options with verification disabled certificates 13 | const sslOptions = { verifySSLCertificate: false }; 14 | // get a HTTPS agent with ssl verification disabled 15 | const agent = fetchAgent.getHttpsAgent(sslOptions); 16 | 17 | // set the agent in the request options 18 | const requestOptions = { 19 | agent: agent, 20 | }; 21 | 22 | // call the endpoint with a self signed certificate 23 | // NOTE: this is a fake endpoint make sure to replace with a real one 24 | const response = await fetch('https://some-website-with-a-self-signde-cert.com', requestOptions); 25 | 26 | //verify that response status is 200 27 | assert.equal(200, response.status); 28 | } 29 | -------------------------------------------------------------------------------- /API-transaction-scripts/proxy+custom-ssl-certificate.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes Transaction script which uses a proxy and a custom SSL certificate. 3 | // 4 | import { fetchAgent, test } from 'thousandeyes'; 5 | import fetch from 'node-fetch'; 6 | import assert from 'assert'; 7 | 8 | runScript(); 9 | 10 | async function runScript() { 11 | // X509 of the certificate 12 | // NOTE: this is a fake certificate make sure to replace with a real one 13 | const cert = `-----BEGIN CERTIFICATE----- 14 | self signed certificate 15 | -----END CERTIFICATE----- 16 | `; 17 | // SSL options with custom certificates 18 | const sslOptions = { customCA: [cert] }; 19 | // test proxy settings 20 | const httpsProxySettings = test.getSettings().proxy; 21 | 22 | // get a HTTPS proxy agent 23 | // NOTE: this works with getHttpProxyAgent and getPACProxyAgent too. 24 | const httpsProxyAgent = fetchAgent.getHttpsProxyAgent(httpsProxySettings, sslOptions); 25 | // set the agent in the request options 26 | const httpsProxyRequestOptions = { 27 | agent: httpsProxyAgent, 28 | }; 29 | // call the endpoint with a self signed certificate 30 | // NOTE: this is a fake endpoint make sure to replace with a real one 31 | const response2 = await fetch('https://some-website.com', httpsProxyRequestOptions); 32 | // verify that response status is 200 33 | assert.equal(200, response2.status); 34 | }; 35 | -------------------------------------------------------------------------------- /API-transaction-scripts/proxy+disable-ssl-verification.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes Transaction script which uses a proxy and a custom SSL certificate. 3 | // 4 | import { fetchAgent, test } from 'thousandeyes'; 5 | import fetch from 'node-fetch'; 6 | import assert from 'assert'; 7 | 8 | runScript(); 9 | 10 | async function runScript() { 11 | // SSL options with verification disabled certificates 12 | const sslOptions = { verifySSLCertificate: false }; 13 | // test proxy settings 14 | const httpsProxySettings = test.getSettings().proxy; 15 | 16 | // get a HTTPS proxy agent 17 | // NOTE: this works with getHttpProxyAgent and getPACProxyAgent too. 18 | const httpsProxyAgent = fetchAgent.getHttpsProxyAgent(httpsProxySettings, sslOptions); 19 | // set the agent in the request options 20 | const httpsProxyRequestOptions = { 21 | agent: httpsProxyAgent, 22 | }; 23 | // call the endpoint with a self signed certificate 24 | // NOTE: this is a fake endpoint make sure to replace with a real one 25 | const response2 = await fetch('https://some-website.com', httpsProxyRequestOptions); 26 | // verify that response status is 200 27 | assert.equal(200, response2.status); 28 | }; 29 | -------------------------------------------------------------------------------- /API-transaction-scripts/simple-net-send-recv.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes transaction script which demonstrates making a low-level TCP connection to GitHub's 3 | // public SSH server. 4 | // 5 | import { markers, net } from 'thousandeyes'; 6 | import assert from 'assert'; 7 | 8 | runCode(); 9 | 10 | async function runCode() { 11 | markers.start('connect'); 12 | // make a TCP connection on port 22 (SSH) to github.com 13 | const sock = await net.connect(22, 'github.com'); 14 | markers.stop('connect'); 15 | 16 | // set the socket's encoding to utf8 (it will be binary otherwise) 17 | sock.setEncoding('utf8'); 18 | 19 | markers.start('get ssh connect message'); 20 | // ...read enough bytes from the server to validate that it's running SSH protocol version 2.0 21 | const response = await sock.read(7); 22 | // close our end of the connection. we won't be sending anything. 23 | await sock.end(); 24 | markers.stop('get ssh connect message'); 25 | 26 | // validate that the response looks like an HTTP response 27 | assert(response.toString().startsWith('SSH-2.0'), 'Response doesn\'t start with \'SSH-2.0\''); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /API-transaction-scripts/simple-tls-send-recv.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes transaction script which demonstrates making a low-level TLS connection, sending 3 | // a message, and reading a result. In this case, we connect to google.com on port 443 (HTTPS) 4 | // and send the text of a standard HTTP/1.1 GET request. After closing our end of the connection, 5 | // we read everything sent by the server, expecting an HTTP/1.1 response. 6 | // 7 | import { markers, net } from 'thousandeyes'; 8 | import assert from 'assert'; 9 | 10 | runCode(); 11 | 12 | async function runCode() { 13 | markers.start('connect'); 14 | // make a TLS connection on port 443, which is the standard HTTPS port 15 | const sock = await net.connectTls(443, 'google.com', { 16 | minVersion: 'TLSv1.2', // we require a minimum TLS version of 1.2 17 | // other supported options are listed in the NodeJS TLS API docs: 18 | // - https://nodejs.org/api/tls.html#tls_tls_connect_options_callback 19 | // - https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options 20 | }); 21 | markers.stop('connect'); 22 | 23 | // set the socket's encoding to utf8 (it will be binary otherwise) 24 | sock.setEncoding('utf8'); 25 | 26 | markers.start('GET request'); 27 | // send a standard HTTP/1.1 GET request. A Host: header for google.com is supplied. 28 | await sock.writeAll('GET / HTTP/1.1\r\nHost: google.com\r\n\r\n'); 29 | // ...close our end of the connection after we've sent the request 30 | await sock.end(); 31 | // ...read every byte of the server's response 32 | const response = await sock.readAll(); 33 | markers.stop('GET request'); 34 | 35 | // validate that the response looks like an HTTP response 36 | assert(response.toString().startsWith('HTTP/1.1'), 'Response doesn\'t start with \'HTTP/1.1\''); 37 | } 38 | -------------------------------------------------------------------------------- /API-transaction-scripts/test-settings-proxy.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes Transaction script which uses proxies with configuration from the test config 3 | // 4 | import {markers, fetchAgent, test } from 'thousandeyes'; 5 | import fetch from 'node-fetch'; 6 | import assert from 'assert'; 7 | 8 | runScript(); 9 | 10 | async function runScript() { 11 | // HTTP proxy 12 | markers.start('HTTP Proxy'); 13 | // test proxy settings 14 | const httpProxySettings = test.getSettings().proxy; 15 | // get a HTTP proxy agent 16 | const httpProxyAgent = fetchAgent.getHttpProxyAgent(httpProxySettings); 17 | // set the agent in the request options 18 | const httpProxyRequestOptions = { 19 | agent: httpProxyAgent, 20 | }; 21 | // call the endpoint with a self signed certificate 22 | // NOTE: this is a fake endpoint make sure to replace with a real one 23 | const response1 = await fetch('http://some-website.com', httpProxyRequestOptions); 24 | // verify that response status is 200 25 | assert.equal(200, response1.status); 26 | markers.end('HTTP Proxy'); 27 | 28 | // HTTPS proxy 29 | markers.start('HTTPS Proxy'); 30 | // test proxy settings 31 | const httpsProxySettings = test.getSettings().proxy; 32 | // get a HTTPS proxy agent 33 | const httpsProxyAgent = fetchAgent.getHttpsProxyAgent(httpsProxySettings); 34 | // set the agent in the request options 35 | const httpsProxyRequestOptions = { 36 | agent: httpsProxyAgent, 37 | }; 38 | // call the endpoint with a self signed certificate 39 | // NOTE: this is a fake endpoint make sure to replace with a real one 40 | const response2 = await fetch('https://some-website.com', httpsProxyRequestOptions); 41 | // verify that response status is 200 42 | assert.equal(200, response2.status); 43 | markers.end('HTTPS Proxy'); 44 | 45 | // PAC proxy 46 | markers.start('PAC Proxy'); 47 | // test proxy settings 48 | const pacProxySettings = test.getSettings().proxy; 49 | // get a PAC proxy agent 50 | const pacProxyAgent = fetchAgent.getPACProxyAgent(pacProxySettings); 51 | // set the agent in the request options 52 | const pacProxyRequestOptions = { 53 | agent: pacProxyAgent, 54 | }; 55 | // call the endpoint with a self signed certificate 56 | // NOTE: this is a fake endpoint make sure to replace with a real one 57 | const response3 = await fetch('https://some-website.com', pacProxyRequestOptions); 58 | // verify that response status is 200 59 | assert.equal(200, response3.status); 60 | markers.end('PAC Proxy'); 61 | }; 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019 ThousandEyes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThousandEyes Transaction Scripting Examples 2 | A collection of example ThousandEyes synthetic transaction scripts that can be used as a reference when creating transaction tests in ThousandEyes or the ThousandEyes Recorder IDE. 3 | 4 | Note - This project contains example code and is not covered under ThousandEyes support. 5 | 6 | 7 | ## Getting Started 8 | To get started scripting, download the [ThousandEyes Recorder](https://app.thousandeyes.com/settings/tests/) from the Transaction test settings page in the ThousandEyes application or use one of the following download links: 9 | * Windows - Windows 7 or later - [Download - Windows exe](https://downloads.thousandeyes.com/tedit/recorder/ThousandEyesRecorderIDE.exe) 10 | * macOS - MacOS 10.10 (Yosemite) or later - [Download - Mac dmg](https://downloads.thousandeyes.com/tedit/recorder/ThousandEyesRecorderIDE.dmg) 11 | * Linux - Ubuntu 12.04 or later - [Download Linux AppImage](https://downloads.thousandeyes.com/tedit/recorder/ThousandEyesRecorderIDE.AppImage) 12 | 13 | New to synthetic scripting? Start by reviewing the ThousandEyes product documentation for [transaction tests](https://docs.thousandeyes.com/product-documentation/browser-synthetics/transaction-tests), and the pages under [Transaction Scripting Reference](https://docs.thousandeyes.com/product-documentation/browser-synthetics/transaction-tests/transaction-scripting-reference). 14 | 15 | ## Resources 16 | * [Getting Started guide](https://github.com/thousandeyes/transaction-scripting-examples/wiki/Getting-started-with-ThousandEyes-Synthetics). 17 | * [Troubleshooting Synthetics scripts](https://github.com/thousandeyes/transaction-scripting-examples/wiki/Troubleshooting-synthetics-scripts). 18 | * [Selenium Webdriver Documentation](https://seleniumhq.github.io/selenium/docs/api/javascript/index.html). 19 | -------------------------------------------------------------------------------- /applications/README.md: -------------------------------------------------------------------------------- 1 | # ThousandEyes - Application Transaction Examples 2 | These .js files are a collection of Javascript synthetic transaction examples for typical user workflows on common SaaS and web applcations. Each of the .js scripts listed here represent a complete transaction test and can be used standalone. 3 | 4 | Where credentials are required the examples will use `myCredentials` and a placeholder for username. 5 | 6 | ## How to Use 7 | 1. Clone the repository or just download/copy an individual .js transaction script file. 8 | 2. In ThousandEyes [create a new application synthetic transaction test](https://www.thousandeyes.com/resources/getting-started-with-transactions-tutorial). 9 | 3. Paste in the contents of .js transaction script file (or select `Import from JS File`) 10 | 4. Set any other test settings and save the test. Recommended to start with 3-5 agents, Round Robin testing with 10/1 minute test intervals. 11 | 5. If any credentials are required create a new credential with `myCredentials` or rename the credential name in the transaction script to use your own. Open the Transaciton test, select the credentials button and check your credential. Make sure your credential name matches what's used in the transaction. 12 | -------------------------------------------------------------------------------- /applications/office-365/README.md: -------------------------------------------------------------------------------- 1 | # Office 365 Transaction Templates 2 | ### Teams 3 | * [teams-chat.js](https://github.com/thousandeyes/transaction-scripting-examples/blob/master/applications/office-365/teams-chat.js) 4 | 5 | ### OneDrive 6 | * [onedrive-download.js](https://github.com/thousandeyes/transaction-scripting-examples/blob/master/applications/office-365/onedrive-download.js) 7 | 8 | ### Sharepoint 9 | * [sharepoint-download.js](https://github.com/thousandeyes/transaction-scripting-examples/blob/master/applications/office-365/sharepoint-download.js) 10 | 11 | ### Outlook 12 | * [outlook-sendemail.js](https://github.com/thousandeyes/transaction-scripting-examples/blob/master/applications/office-365/outlook-sendemail.js) 13 | 14 | ### Word 15 | * [word-login.js](https://github.com/thousandeyes/transaction-scripting-examples/blob/master/applications/office-365/word-login.js) 16 | 17 | ### Excel 18 | * [excel-login.js](https://github.com/thousandeyes/transaction-scripting-examples/blob/master/applications/office-365/excel-login.js) 19 | 20 | ### PowerPoint 21 | * [powerpoint-login.js](https://github.com/thousandeyes/transaction-scripting-examples/blob/master/applications/office-365/powerpoint-login.js) 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /applications/office-365/excel-login.js: -------------------------------------------------------------------------------- 1 | import { By, Key, until } from 'selenium-webdriver'; 2 | import { driver, credentials, markers } from 'thousandeyes'; 3 | 4 | // Template Values 5 | const loginEmail = '<>'; //'tonystark@acmehero.onmicrosoft.com'; 6 | const credentialName = '<>'; //'tonystark'; 7 | 8 | runScript(); 9 | 10 | async function runScript() { 11 | await configureDriver(); 12 | markers.start('Page Load'); 13 | await driver.get('https://excel.office.com'); 14 | markers.stop('Page Load'); 15 | 16 | await driver.takeScreenshot(); 17 | 18 | // Login 19 | markers.start('Login'); 20 | await typeText(loginEmail, By.id(`i0116`)); 21 | await click(By.id(`idSIButton9`)); 22 | await typeText(credentials.get(credentialName), By.id(`i0118`)); 23 | await click(By.id(`idSIButton9`)); 24 | markers.stop('Login'); 25 | } 26 | 27 | async function configureDriver() { 28 | return driver.manage().setTimeouts({ 29 | implicit: 5 * 1000 // If an element is not found, reattempt for this many milliseconds 30 | }); 31 | } 32 | 33 | async function typeText(value, selector) { 34 | await simulateHumanDelay(); 35 | await driver.findElement(selector). 36 | sendKeys(value); 37 | } 38 | 39 | async function simulateHumanDelay() { 40 | await driver.sleep(550); 41 | } 42 | 43 | async function click(selector) { 44 | await simulateHumanDelay(); 45 | 46 | const configuredTimeouts = await driver.manage().getTimeouts(); 47 | const clickAttemptEndTime = Date.now() + configuredTimeouts.implicit; 48 | 49 | await reattemptUntil(attemptToClick, clickAttemptEndTime); 50 | 51 | async function attemptToClick() { 52 | await driver.wait(() => isElementClickable(selector), configuredTimeouts.implicit); 53 | await driver.findElement(selector). 54 | click(); 55 | } 56 | } 57 | 58 | async function isElementClickable(selector) { 59 | try { 60 | return await driver.findElement(selector).isDisplayed(); 61 | } 62 | catch (error) { 63 | return false; // Will throw an error if element is not connected to the document 64 | } 65 | } 66 | 67 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 68 | const TIME_BETWEEN_ATTEMPTS = 100; 69 | let numberOfAttempts = 0; 70 | let attemptError; 71 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 72 | try { 73 | numberOfAttempts += 1; 74 | await attemptActionFn(); 75 | } 76 | catch (error) { 77 | attemptError = error; 78 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 79 | continue; // Attempt failed, reattempt 80 | } 81 | attemptError = null; 82 | break; // Attempt succeeded, stop attempting 83 | } 84 | 85 | const wasAttemptSuccessful = !attemptError; 86 | if (!wasAttemptSuccessful) { 87 | throw attemptError; 88 | } 89 | } -------------------------------------------------------------------------------- /applications/office-365/exchange-ews-list-inbox.js: -------------------------------------------------------------------------------- 1 | /* 2 | A script that monitors Office 365 / Exchange mail services used by Outlook clients. 3 | This script lists Inbox and measures the response of the EWS API. 4 | More https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/start-using-web-services-in-exchange 5 | Author: primoz@thousandeyes.com 6 | */ 7 | 8 | import { markers, credentials } from 'thousandeyes'; 9 | import fetch from 'node-fetch'; 10 | import assert from 'assert'; 11 | 12 | // EWS endpoint URL, typically https:///EWS/Exchange.asmx for Exchange, 13 | // or https://outlook.office365.com/EWS/Exchange.asmx for Office 365. 14 | let url = 'https://outlook.office365.com/EWS/Exchange.asmx'; 15 | let username = '<>' // thousandeyestest@outlook.com 16 | // Base64 hash of 'username:password'. You can create one locally with: 17 | // echo -n thousandeyestest\@outlook.com:pa$$w0rd | base64 18 | // (don't forget to escape @ with \) 19 | let base64credentials = '<>'; 20 | 21 | let body = ` 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | IdOnly 31 | 32 | 33 | 34 | 35 | ` + username + ` 36 | 37 | 38 | 39 | 40 | 41 | 42 | `; 43 | 44 | runScript(); 45 | 46 | async function runScript() { 47 | 48 | await markers.start('Open Inbox'); 49 | let response = await fetch(url, { 50 | method: "POST", 51 | headers: { 52 | "Content-Type": "text/xml; charset=utf-8", 53 | "Authorization": "Basic " + base64credentials 54 | }, 55 | body: body.trim() 56 | }); 57 | await markers.stop('Open Inbox'); 58 | 59 | if (response.status != 200) { 60 | console.log("HTTP Error: " + response.status + " " + response.statusText); 61 | await assert(false, "HTTP Error: " + response.status + " " + response.statusText); 62 | } 63 | 64 | let content = await response.text(); 65 | 66 | let responseCode; 67 | let res = content.match(/(.*)<\/m:ResponseCode>/); 68 | if ((res) && (res.length > 1)) { 69 | responseCode = res[1] 70 | } else { 71 | // Unhandled error 72 | console.log("Error: " + content); 73 | await assert(false, "Error: " + content); 74 | } 75 | 76 | if (responseCode == 'NoError') { 77 | console.log('Inbox opened!'); 78 | } else { 79 | res = content.match(/(.*)<\/m:MessageText>/); 80 | if ((res) && (res.length > 1)) { 81 | console.log("Response Error: " + responseCode + " - " + res[1]); 82 | await assert(false, "Response Error: " + responseCode + " - " + res[1]); 83 | } else { 84 | console.log("Response Error: " + responseCode); 85 | await assert(false, "Response Error: " + responseCode); 86 | } 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /applications/office-365/exchange-ews-oauth-list-inbox.js: -------------------------------------------------------------------------------- 1 | /* 2 | A script that monitors Office 365 / Exchange mail services used by Outlook clients. 3 | This script lists Inbox and measures the response of the EWS API. 4 | This script uses the OAuth authentication. 5 | Author: primoz@thousandeyes.com 6 | */ 7 | 8 | import { markers, credentials } from 'thousandeyes'; 9 | import fetch from 'node-fetch'; 10 | 11 | 12 | let username = '<>'; 13 | // You obtain the below values when you register ThousandEyes Web Script as an Azure AD app 14 | // See: https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth 15 | let tenantId = '<>'; 16 | let clientId = '
<>'; 17 | let secretToken = credentials.get('ThousandEyes Azure AD Secret'); 18 | 19 | let oauthUrl = 'https://login.microsoftonline.com/' + tenantId + '/oauth2/token'; 20 | let ewsUrl = 'https://outlook.office365.com/EWS/Exchange.asmx'; 21 | 22 | let ewsBody = ` 23 | 24 | 25 | 26 | 27 | 28 | 29 | ` + username + ` 30 | 31 | 32 | 33 | 34 | 35 | 36 | IdOnly 37 | 38 | 39 | 40 | 41 | ` + username + ` 42 | 43 | 44 | 45 | 46 | 47 | 48 | `; 49 | 50 | runScript(); 51 | 52 | async function runScript() { 53 | 54 | /* 55 | * OAuth authentication 56 | */ 57 | 58 | await markers.start('OAuth Authentication'); 59 | let body = { 60 | grant_type: 'client_credentials', 61 | client_id: clientId, 62 | client_secret: secretToken, 63 | tenant_id: tenantId, 64 | resource: 'https://outlook.office365.com/' 65 | } 66 | 67 | let bodyText = ''; 68 | for (let key in body) { 69 | bodyText += key + "=" + encodeURIComponent(body[key]) + "&"; 70 | } 71 | let response = await fetch(oauthUrl, { 72 | method: "POST", 73 | headers: { 74 | 'Content-Type': 'application/x-www-form-urlencoded', 75 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36' 76 | }, 77 | body: bodyText 78 | }); 79 | await markers.stop('OAuth Authentication'); 80 | 81 | if (response.status != 200) { 82 | console.log("OAuth query HTTP Error: " + response.status + " " + response.statusText); 83 | console.log(await response.text()); 84 | throw Error("OAuth HTTP Error: " + response.status + " " + response.statusText); 85 | } 86 | 87 | let responseJson = await response.json(); 88 | let accessToken = responseJson['access_token']; 89 | 90 | /* 91 | * EWS API query 92 | */ 93 | 94 | await markers.start('Open Inbox'); 95 | response = await fetch(ewsUrl, { 96 | method: "POST", 97 | headers: { 98 | "Content-Type": "text/xml; charset=utf-8", 99 | "Authorization": "Bearer " + accessToken 100 | }, 101 | body: ewsBody.trim() 102 | }); 103 | await markers.stop('Open Inbox'); 104 | 105 | if (response.status != 200) { 106 | console.log("HTTP Error: " + response.status + " " + response.statusText); 107 | throw Error("HTTP Error: " + response.status + " " + response.statusText); 108 | } 109 | 110 | let content = await response.text(); 111 | 112 | let responseCode; 113 | let res = content.match(/(.*)<\/m:ResponseCode>/); 114 | if ((res) && (res.length > 1)) { 115 | responseCode = res[1] 116 | } else { 117 | // Unhandled error 118 | console.log("Error: " + content); 119 | throw Error("Error: " + content); 120 | } 121 | 122 | if (responseCode == 'NoError') { 123 | console.log('Inbox opened!'); 124 | } else { 125 | res = content.match(/(.*)<\/m:MessageText>/); 126 | if ((res) && (res.length > 1)) { 127 | console.log("Response Error: " + responseCode + " - " + res[1]); 128 | throw Error("Response Error: " + responseCode + " - " + res[1]); 129 | } else { 130 | console.log("Response Error: " + responseCode); 131 | throw Error("Response Error: " + responseCode); 132 | } 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /applications/office-365/exchange-ews-send-email.js: -------------------------------------------------------------------------------- 1 | /* 2 | A script that monitors Office 365 / Exchange mail services used by Outlook clients. 3 | This script sends an email and measures the response of the EWS API. 4 | More https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/start-using-web-services-in-exchange 5 | Author: primoz@thousandeyes.com 6 | */ 7 | 8 | import { markers, credentials } from 'thousandeyes'; 9 | import fetch from 'node-fetch'; 10 | import assert from 'assert'; 11 | 12 | // EWS endpoint URL, typically https:///EWS/Exchange.asmx for Exchange, 13 | // or https://outlook.office365.com/EWS/Exchange.asmx for Office 365. 14 | let url = 'https://outlook.office365.com/EWS/Exchange.asmx'; 15 | let username = '<>' // thousandeyestest@outlook.com 16 | // Base64 hash of 'username:password'. You can create one locally with: 17 | // echo -n thousandeyestest\@outlook.com:pa$$w0rd | base64 18 | // (don't forget to escape @ with \) 19 | let base64credentials = '<>'; 20 | let emailRecipient = '<>'; // primoz@thousandeyes.com 21 | 22 | let emailSender = username; 23 | let emailSenderName = 'ThousandEyes EWS Transaction Test'; 24 | let emailSubject = 'ThousandEyes EWS Transaction Test'; 25 | let emailBody = 'This email has been sent from a ThousandEyes transaction test using Exchange EWS API.'; 26 | 27 | 28 | let body = ` 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ` + emailSubject + ` 39 | ` + emailBody + ` 40 | 41 | 42 | ` + emailRecipient + ` 43 | 44 | 45 | 46 | 47 | ` + emailSenderName + ` 48 | ` + emailSender + ` 49 | 50 | 51 | false 52 | 53 | 54 | 55 | 56 | 57 | `; 58 | 59 | runScript(); 60 | 61 | async function runScript() { 62 | 63 | await markers.start('Send Email'); 64 | let response = await fetch(url, { 65 | method: "POST", 66 | headers: { 67 | "Content-Type": "text/xml; charset=utf-8", 68 | "Authorization": "Basic " + base64credentials 69 | }, 70 | body: body.trim() 71 | }); 72 | await markers.stop('Send Email'); 73 | 74 | if (response.status != 200) { 75 | console.log("HTTP Error: " + response.status + " " + response.statusText); 76 | await assert(false, "HTTP Error: " + response.status + " " + response.statusText); 77 | } 78 | 79 | let content = await response.text(); 80 | 81 | let responseCode; 82 | let res = content.match(/(.*)<\/m:ResponseCode>/); 83 | if ((res) && (res.length > 1)) { 84 | responseCode = res[1] 85 | } else { 86 | // Unhandled error 87 | console.log("Error: " + content); 88 | await assert(false, "Error: " + content); 89 | } 90 | 91 | if (responseCode == 'NoError') { 92 | console.log('Email sent!'); 93 | } else { 94 | res = content.match(/(.*)<\/m:MessageText>/); 95 | if ((res) && (res.length > 1)) { 96 | console.log("Response Error: " + responseCode + " - " + res[1]); 97 | await assert(false, "Response Error: " + responseCode + " - " + res[1]); 98 | } else { 99 | console.log("Response Error: " + responseCode); 100 | await assert(false, "Response Error: " + responseCode); 101 | } 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /applications/office-365/graphapi-mail-inbox.js: -------------------------------------------------------------------------------- 1 | /* 2 | A script that monitors Office 365 / Exchange mail services used by Outlook clients. 3 | This script lists Inbox and measures the response of the Graph API 4 | This script uses the OAuth authentication. 5 | Author: primoz@thousandeyes.com 6 | */ 7 | 8 | import { markers, credentials, test, fetchAgent } from 'thousandeyes'; 9 | import fetch from 'node-fetch'; 10 | 11 | let username = credentials.get('Microsoft Username'); 12 | let password = credentials.get('Microsoft Password'); 13 | let clientId = credentials.get('Microsoft Client ID'); 14 | let clientSecret = credentials.get('Microsoft Client Secret'); 15 | 16 | runScript(); 17 | 18 | async function runScript() { 19 | 20 | let testSettings = await test.getSettings(); 21 | let httpProxySettings = testSettings.proxy; 22 | let sslOptions = {}; // do this to enforce SSL through CONNECT 23 | let httpProxyAgent = fetchAgent.getHttpProxyAgent(httpProxySettings, sslOptions); 24 | 25 | /* 26 | * OAuth authentication 27 | */ 28 | 29 | await markers.start('OAuth Authentication'); 30 | let body = { 31 | grant_type: 'password', 32 | client_id: clientId, 33 | client_secret: clientSecret, 34 | username: username, 35 | password: password, 36 | scope: 'mail.read mail.readwrite mail.send calendars.read calendars.readwrite user.read files.read files.readwrite openid profile offline_access' 37 | } 38 | 39 | let bodyText = ''; 40 | for (let key in body) { 41 | bodyText += key + "=" + encodeURIComponent(body[key]) + "&"; 42 | } 43 | 44 | let oauthFetchData = { 45 | method: "POST", 46 | headers: { 47 | 'Content-Type': 'application/x-www-form-urlencoded' 48 | }, 49 | body: bodyText 50 | } 51 | if (httpProxySettings.type != 'NONE') { 52 | oauthFetchData['agent'] = httpProxyAgent; 53 | } 54 | let response = await fetch('https://login.microsoftonline.com/organizations/oauth2/v2.0/token', oauthFetchData); 55 | if (response.status != 200) { 56 | console.warn("OAuth HTTP Error: " + response.status + " " + response.statusText); 57 | console.warn(await response.text()); 58 | throw Error("OAuth HTTP Error: " + response.status + " " + response.statusText); 59 | } 60 | await markers.stop('OAuth Authentication'); 61 | 62 | let responseJson = await response.json(); 63 | let accessToken = responseJson['access_token']; 64 | 65 | /* 66 | * API query 67 | */ 68 | 69 | await markers.start('Open Inbox'); 70 | let apiFetchData = { 71 | method: "GET", 72 | headers: { 73 | "Content-Type": "text/xml; charset=utf-8", 74 | "Authorization": "Bearer " + accessToken 75 | } 76 | } 77 | if (httpProxySettings.type != 'NONE') { 78 | apiFetchData['agent'] = httpProxyAgent; 79 | } 80 | response = await fetch('https://graph.microsoft.com/v1.0/me/mailFolders/Inbox/messages', apiFetchData); 81 | 82 | if (response.status != 200) { 83 | console.warn("API HTTP Error: " + response.status + " " + response.statusText); 84 | console.warn(await response.text()); 85 | throw Error("API HTTP Error: " + response.status + " " + response.statusText); 86 | } 87 | await markers.stop('Open Inbox'); 88 | 89 | responseJson = await response.json(); 90 | 91 | if ('value' in responseJson) { 92 | console.warn('Number of messages: ' + responseJson['value'].length); 93 | } else { 94 | throw Error("API returned no 'value' parameter."); 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /applications/office-365/graphapi-oauth-list-inbox.js: -------------------------------------------------------------------------------- 1 | /* 2 | A script that monitors Office 365 / Exchange mail services used by Outlook clients. 3 | This script lists Inbox and measures the response of the Graph API 4 | This script uses the OAuth authentication. 5 | 6 | Author: primoz@thousandeyes.com 7 | 8 | Prerequisites: 9 | - register ThousandEyes app in AzureAD with permissions listed in line 43, get app Client ID & Secret 10 | - create Cloud-only user in AzureAD or ADFS user with AzureAD password has sync, get user Username & Password 11 | - open https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize?client_id=CLIENT_ID&scope=mail.read%20mail.readwrite%20mail.send%20calendars.read%20calendars.readwrite%20user.read%20files.read%20files.readwrite%20openid%20profile%20offline_access&response_type=code 12 | in incognito browser window, login with user credentials and authorize the ThousandEyes app 13 | */ 14 | 15 | import { markers, credentials, test, fetchAgent } from 'thousandeyes'; 16 | import fetch from 'node-fetch'; 17 | 18 | let username = credentials.get('Microsoft Username'); 19 | let password = credentials.get('Microsoft Password'); 20 | let clientId = credentials.get('Microsoft Client ID'); 21 | let clientSecret = credentials.get('Microsoft Client Secret'); 22 | 23 | runScript(); 24 | 25 | async function runScript() { 26 | 27 | let testSettings = await test.getSettings(); 28 | let httpProxySettings = testSettings.proxy; 29 | let sslOptions = {}; // do this to enforce SSL through CONNECT 30 | let httpProxyAgent = fetchAgent.getHttpProxyAgent(httpProxySettings, sslOptions); 31 | 32 | /* 33 | * OAuth authentication 34 | */ 35 | 36 | await markers.start('OAuth Authentication'); 37 | let body = { 38 | grant_type: 'password', 39 | client_id: clientId, 40 | client_secret: clientSecret, 41 | username: username, 42 | password: password, 43 | scope: 'mail.read mail.readwrite mail.send calendars.read calendars.readwrite user.read files.read files.readwrite openid profile offline_access' 44 | } 45 | 46 | let bodyText = ''; 47 | for (let key in body) { 48 | bodyText += key + "=" + encodeURIComponent(body[key]) + "&"; 49 | } 50 | 51 | let oauthFetchData = { 52 | method: "POST", 53 | headers: { 54 | 'Content-Type': 'application/x-www-form-urlencoded' 55 | }, 56 | body: bodyText 57 | } 58 | if (httpProxySettings.type != 'NONE') { 59 | oauthFetchData['agent'] = httpProxyAgent; 60 | } 61 | let response = await fetch('https://login.microsoftonline.com/organizations/oauth2/v2.0/token', oauthFetchData); 62 | if (response.status != 200) { 63 | console.warn("OAuth HTTP Error: " + response.status + " " + response.statusText); 64 | console.warn(await response.text()); 65 | throw Error("OAuth HTTP Error: " + response.status + " " + response.statusText); 66 | } 67 | await markers.stop('OAuth Authentication'); 68 | 69 | let responseJson = await response.json(); 70 | let accessToken = responseJson['access_token']; 71 | 72 | /* 73 | * API query 74 | */ 75 | 76 | await markers.start('Open Inbox'); 77 | let apiFetchData = { 78 | method: "GET", 79 | headers: { 80 | "Content-Type": "text/xml; charset=utf-8", 81 | "Authorization": "Bearer " + accessToken 82 | } 83 | } 84 | if (httpProxySettings.type != 'NONE') { 85 | apiFetchData['agent'] = httpProxyAgent; 86 | } 87 | response = await fetch('https://graph.microsoft.com/v1.0/me/mailFolders/Inbox/messages', apiFetchData); 88 | 89 | if (response.status != 200) { 90 | console.warn("API HTTP Error: " + response.status + " " + response.statusText); 91 | console.warn(await response.text()); 92 | throw Error("API HTTP Error: " + response.status + " " + response.statusText); 93 | } 94 | await markers.stop('Open Inbox'); 95 | 96 | responseJson = await response.json(); 97 | 98 | if ('value' in responseJson) { 99 | console.warn('Number of messages: ' + responseJson['value'].length); 100 | } else { 101 | throw Error("API returned no 'value' parameter."); 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /applications/office-365/onedrive-download.js: -------------------------------------------------------------------------------- 1 | import { By, Key } from 'selenium-webdriver'; 2 | import { driver, markers, credentials, downloads } from 'thousandeyes'; 3 | 4 | // Template Values 5 | let domain = '<>'; // 'acmehero' 6 | let loginEmail = '< isElementClickable(selector), configuredTimeouts.implicit); 53 | await driver.findElement(selector). 54 | click(); 55 | } 56 | } 57 | 58 | async function isElementClickable(selector) { 59 | try { 60 | return await driver.findElement(selector).isDisplayed(); 61 | } 62 | catch (error) { 63 | return false; // Will throw an error if element is not connected to the document 64 | } 65 | } 66 | 67 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 68 | const TIME_BETWEEN_ATTEMPTS = 100; 69 | let numberOfAttempts = 0; 70 | let attemptError; 71 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 72 | try { 73 | numberOfAttempts += 1; 74 | await attemptActionFn(); 75 | } 76 | catch (error) { 77 | attemptError = error; 78 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 79 | continue; // Attempt failed, reattempt 80 | } 81 | attemptError = null; 82 | break; // Attempt succeeded, stop attempting 83 | } 84 | 85 | const wasAttemptSuccessful = !attemptError; 86 | if (!wasAttemptSuccessful) { 87 | throw attemptError; 88 | } 89 | } -------------------------------------------------------------------------------- /applications/office-365/sharepoint-login-download.js: -------------------------------------------------------------------------------- 1 | import { By, Key, until } from 'selenium-webdriver'; 2 | import { driver, markers, credentials, downloads, transaction } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | 8 | // Configure 9 | await configureDriver(); 10 | 11 | // Page Load 12 | markers.start('Page Load'); 13 | 14 | // TODO: Replace with your sharepoint site URL (ex yourcompany.sharepoint.com/sites/YourSite 15 | await driver.get('https://'); 16 | var actualTitle = await driver.getTitle(); 17 | markers.stop('Page Load'); 18 | await driver.takeScreenshot(); 19 | 20 | // Login process 21 | markers.start('Username'); 22 | await click(By.id(`i0116`)); 23 | 24 | // TODO: Replace with your sharepoint login email 25 | await typeText('', By.id(`i0116`)); 26 | await click(By.id(`idSIButton9`)); 27 | markers.stop('Username'); 28 | 29 | // Enter Password 30 | // TODO: Add 'myCredentials' to ThousandEyes credential manager 31 | markers.start('Password'); 32 | await click(By.id(`i0118`)); 33 | await typeText(credentials.get('myCredentials'), By.id(`i0118`)); 34 | 35 | // Click on 'Sign in' 36 | await isElementClickable(By.id(`idSIButton9`)); 37 | await click(By.id(`idSIButton9`)); 38 | 39 | // Click on 'No' (do not stay signed in popup) 40 | await isElementClickable(By.id(`idBtn_Back`)); 41 | markers.stop('Password'); 42 | markers.start('Shared Documents'); 43 | await click(By.id(`idBtn_Back`)); 44 | 45 | // Wait for backend page to load 46 | await driver.wait(until.titleContains('Home')); 47 | 48 | // Navigate to 'Shared Documents' 49 | await click(By.css(`[href="https:///Shared Documents"]`)); 50 | 51 | // Wait for shared docs page to load 52 | await driver.wait(until.titleContains('All Documents')); 53 | 54 | // Select a file 55 | // TODO: replace with the name of the file your downloading; eg. Public Cloud Performance Benchmark Report Final.pdf 56 | await click(By.css(`[aria-label="Checkbox for "]`)); 57 | markers.stop('Shared Documents'); 58 | 59 | await driver.takeScreenshot(); 60 | 61 | // Click on 'Download' and measure download time 62 | // TODO: replace with the name of the file your downloading; eg. Public Cloud Performance Benchmark Report Final.pdf 63 | 64 | markers.start('Download'); 65 | await click(By.css(`[data-icon-name="download"]`)); 66 | await downloads.waitForDownload('', 60000); 67 | markers.stop('Download'); 68 | }; 69 | 70 | async function configureDriver() { 71 | return driver.manage().setTimeouts({ 72 | implicit: 10 * 1000, // If an element is not found, reattempt for this many milliseconds 73 | }); 74 | } 75 | 76 | async function typeText(value, selector) { 77 | await simulateHumanDelay(); 78 | await driver.findElement(selector) 79 | .sendKeys(value); 80 | } 81 | 82 | async function simulateHumanDelay() { 83 | await driver.sleep(550); 84 | } 85 | 86 | 87 | async function click(selector) { 88 | await simulateHumanDelay(); 89 | 90 | const configuredTimeouts = await driver.manage().getTimeouts(); 91 | const clickAttemptEndTime = Date.now() + configuredTimeouts.implicit; 92 | 93 | await reattemptUntil(attemptToClick, clickAttemptEndTime); 94 | async function attemptToClick() { 95 | await driver.findElement(selector) 96 | .click().then(null, async function (err) { 97 | await driver.wait(() => isElementClickable(selector), configuredTimeouts.implicit); 98 | await driver.findElement(selector) 99 | .click(); 100 | }); 101 | } 102 | } 103 | 104 | async function isElementClickable(selector) { 105 | try { 106 | return await driver.findElement(selector).isDisplayed(); 107 | } 108 | catch (error) { 109 | return false; // Will throw an error if element is not connected to the document 110 | } 111 | } 112 | 113 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 114 | const TIME_BETWEEN_ATTEMPTS = 100; 115 | let numberOfAttempts = 0; 116 | let attemptError; 117 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 118 | try { 119 | numberOfAttempts += 1; 120 | await attemptActionFn(); 121 | } 122 | catch (error) { 123 | attemptError = error; 124 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 125 | continue; // Attempt failed, reattempt 126 | } 127 | attemptError = null; 128 | break; // Attempt succeeded, stop attempting 129 | } 130 | 131 | const wasAttemptSuccessful = !attemptError; 132 | if (!wasAttemptSuccessful) { 133 | throw attemptError; 134 | } 135 | } 136 | 137 | async function rightClick(selector) { 138 | const element = await driver.findElement(selector); 139 | await driver.actions({ bridge: true }).contextClick(element).perform(); 140 | } 141 | -------------------------------------------------------------------------------- /applications/office-365/teams-chat.js: -------------------------------------------------------------------------------- 1 | import { By, Key, until } from 'selenium-webdriver'; 2 | import { driver, credentials, markers } from 'thousandeyes'; 3 | 4 | // Template Parameters 5 | let email = '<>'; // tonystark@acmehero.onmicrosoft.com'; 6 | let targetUser = '<>'; // 'Pepper Potts'; 7 | let credentialsName = '<>'; // 'tonystark'; 8 | 9 | let markerTimes = {}; 10 | 11 | runScript(); 12 | 13 | async function runScript() { 14 | await configureDriver(); 15 | 16 | // TIP: Console.log prints timestamp 17 | await console.log("Page Load"); 18 | 19 | markerStart('Page Load'); 20 | await driver.get('https://teams.microsoft.com'); 21 | markerStop('Page Load'); 22 | 23 | markerStart("Login"); 24 | await click(By.css(`[name="loginfmt"]`)); 25 | await typeText(email, By.css(`[name="loginfmt"]`)); 26 | 27 | // Click on 'Next' 28 | await click(By.css(`[value="Next"]`)); 29 | 30 | // Click on 'Enter the password for tony@acmeher...' 31 | await click(By.css(`[name="passwd"]`)); 32 | await typeText(credentials.get(credentialsName), By.css(`[name="passwd"]`)); 33 | 34 | // Click on 'Sign in' 35 | await click(By.id(`idSIButton9`)); 36 | 37 | // Click on 'No' (Do not stay signed in) 38 | // This click loads the main teams page 39 | await markerClick(By.id('idBtn_Back'), "Login", "Backend Load"); 40 | 41 | // Wait for chat page to load 42 | await driver.wait(until.elementIsVisible(await driver.findElement(By.css(`.team-information`))), 30, "Teams Landing Page Failed to Load"); 43 | 44 | // Dismiss popups 45 | await clickPeriodic(By.css(`[data-tid="closeModelDialogBtn"]`), 200); 46 | await clickPeriodic(By.css(`[aria-label="Dismiss"]`), 200); 47 | markerStop("Backend Load"); 48 | 49 | await driver.takeScreenshot(); 50 | 51 | markerStart("Compose Message"); 52 | 53 | // Start a new chat 54 | await click(By.css(`[aria-label="Chat Toolbar more options"]`)); 55 | 56 | //Click "New Chat (Alt+N)" 57 | await click(By.css(`[aria-label="New Chat (Alt+N)"]`)); 58 | 59 | 60 | await typeText(targetUser, By.css(`[data-tid="peoplePicker"]`)); 61 | 62 | // Make driver sleep for 1 seconds 63 | await driver.sleep(1000); 64 | 65 | await driver.findElement(By.css(`[data-tid="peoplePicker"]`)).sendKeys(Key.RETURN); 66 | 67 | // Make driver sleep for 5 seconds 68 | await driver.sleep(2000); 69 | 70 | markerStop("Compose Message"); 71 | markerStart("Send Message"); 72 | 73 | // Send a message 74 | let message = "Hello " + Date.now(); 75 | await console.log("Sending " + message); 76 | 77 | await driver.findElement(By.css('[aria-label="Type a new message, editing"]')).sendKeys(message, Key.ENTER); 78 | 79 | await driver.sleep(500); 80 | 81 | // Find all messages 82 | await findElementWithText(message); 83 | 84 | markerStop("Send Message"); 85 | await console.log("Found " + message); 86 | 87 | await driver.takeScreenshot(); 88 | } 89 | 90 | async function configureDriver() { 91 | await driver.manage().window().setRect({ 92 | width: 1200, 93 | height: 983 }); 94 | 95 | await driver.manage().setTimeouts({ 96 | implicit: 10 * 10000 // If an element is not found, reattempt for this many milliseconds 97 | }); 98 | } 99 | 100 | async function click(selector) { 101 | await simulateHumanDelay(); 102 | const configuredTimeouts = await driver.manage().getTimeouts(); 103 | const clickAttemptEndTime = Date.now() + configuredTimeouts.implicit; 104 | await reattemptUntil(attemptToClick, clickAttemptEndTime); 105 | async function attemptToClick() { 106 | await driver.findElement(selector). 107 | click(); 108 | } 109 | } 110 | 111 | async function moveMouseInto(element) { 112 | await driver.actions({ bridge: true }) 113 | .move({ x: -1, y: 0, origin: element }) 114 | .move({ x: 1, y: 0, origin: element }) 115 | .perform(); 116 | } 117 | 118 | async function markerClick(selector, stop, start) { 119 | await driver.findElement(selector); 120 | await markerStop(stop); 121 | await markerStart(start); 122 | await click(selector); 123 | } 124 | 125 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 126 | const TIME_BETWEEN_ATTEMPTS = 100; 127 | let numberOfAttempts = 0; 128 | let attemptError; 129 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 130 | try { 131 | numberOfAttempts += 1; 132 | await attemptActionFn(); 133 | } 134 | catch (error) { 135 | attemptError = error; 136 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 137 | continue; // Attempt failed, reattempt 138 | } 139 | attemptError = null; 140 | break; // Attempt succeeded, stop attempting 141 | } 142 | 143 | const wasAttemptSuccessful = !attemptError; 144 | if (!wasAttemptSuccessful) { 145 | throw attemptError; 146 | } 147 | } 148 | 149 | async function simulateHumanDelay() { 150 | await driver.sleep(550); 151 | } 152 | 153 | async function typeText(value, selector) { 154 | await simulateHumanDelay(); 155 | const element = await driver.findElement(selector); 156 | await element.clear(); 157 | await element.sendKeys(value); 158 | } 159 | 160 | async function clickPeriodic(selector, wait) { 161 | const imp = (await driver.manage().getTimeouts()).implicit; 162 | await driver.manage().setTimeouts({implicit: wait}); 163 | const len = (await driver.findElements(selector)).length; 164 | if (len > 0) { 165 | await console.log("Clicking " + selector); 166 | await click(selector); 167 | } 168 | await driver.manage().setTimeouts({implicit: imp}); 169 | } 170 | 171 | async function findElementWithText(text) { 172 | return await driver.findElement(By.xpath(`//*[text()="${text}"]`)); 173 | } 174 | 175 | async function clickWithText(text) { 176 | return await click(By.xpath(`//*[text()="${text}"]`)); 177 | } 178 | 179 | async function markerStart(marker) { 180 | markerTimes[marker] = Date.now(); 181 | markers.start(marker); 182 | } 183 | 184 | async function markerStop(marker) { 185 | markers.stop(marker); 186 | console.log(marker + ": " + (Date.now() - markerTimes[marker]) + "ms"); 187 | } 188 | -------------------------------------------------------------------------------- /applications/office-365/word-login.js: -------------------------------------------------------------------------------- 1 | import { By, Key, until } from 'selenium-webdriver'; 2 | import { driver, credentials, markers } from 'thousandeyes'; 3 | 4 | // Template Values 5 | const loginEmail = '<>'; //'tonystark@acmehero.onmicrosoft.com'; 6 | const credentialName = '<>'; //'tonystark'; 7 | 8 | runScript(); 9 | 10 | async function runScript() { 11 | await configureDriver(); 12 | markers.start('Page Load'); 13 | await driver.get('https://word.office.com'); 14 | markers.stop('Page Load'); 15 | 16 | await driver.takeScreenshot(); 17 | 18 | // Login 19 | markers.start('Login'); 20 | await typeText(loginEmail, By.id(`i0116`)); 21 | await click(By.id(`idSIButton9`)); 22 | await typeText(credentials.get(credentialName), By.id(`i0118`)); 23 | await click(By.id(`idSIButton9`)); 24 | markers.stop('Login'); 25 | } 26 | 27 | async function configureDriver() { 28 | return driver.manage().setTimeouts({ 29 | implicit: 5 * 1000 // If an element is not found, reattempt for this many milliseconds 30 | }); 31 | } 32 | 33 | async function typeText(value, selector) { 34 | await simulateHumanDelay(); 35 | await driver.findElement(selector). 36 | sendKeys(value); 37 | } 38 | 39 | async function simulateHumanDelay() { 40 | await driver.sleep(550); 41 | } 42 | 43 | async function click(selector) { 44 | await simulateHumanDelay(); 45 | 46 | const configuredTimeouts = await driver.manage().getTimeouts(); 47 | const clickAttemptEndTime = Date.now() + configuredTimeouts.implicit; 48 | 49 | await reattemptUntil(attemptToClick, clickAttemptEndTime); 50 | 51 | async function attemptToClick() { 52 | await driver.wait(() => isElementClickable(selector), configuredTimeouts.implicit); 53 | await driver.findElement(selector). 54 | click(); 55 | } 56 | } 57 | 58 | async function isElementClickable(selector) { 59 | try { 60 | return await driver.findElement(selector).isDisplayed(); 61 | } 62 | catch (error) { 63 | return false; // Will throw an error if element is not connected to the document 64 | } 65 | } 66 | 67 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 68 | const TIME_BETWEEN_ATTEMPTS = 100; 69 | let numberOfAttempts = 0; 70 | let attemptError; 71 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 72 | try { 73 | numberOfAttempts += 1; 74 | await attemptActionFn(); 75 | } 76 | catch (error) { 77 | attemptError = error; 78 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 79 | continue; // Attempt failed, reattempt 80 | } 81 | attemptError = null; 82 | break; // Attempt succeeded, stop attempting 83 | } 84 | 85 | const wasAttemptSuccessful = !attemptError; 86 | if (!wasAttemptSuccessful) { 87 | throw attemptError; 88 | } 89 | } -------------------------------------------------------------------------------- /applications/salesforce/SF_lightning_case_load.js: -------------------------------------------------------------------------------- 1 | import { By, Key, until } from 'selenium-webdriver'; 2 | import { driver, credentials, markers } from 'thousandeyes'; 3 | 4 | // TODO: update with your specific login and password credentalName 5 | const loginEmail = '<>'; // 'user@example.com' 6 | // use the same credentialName as what's used to store the Salesforce password in your ThousandEyes credentials repository 7 | const credentialName = '<>'; 8 | 9 | runScript(); 10 | 11 | async function runScript() { 12 | await configureDriver(); 13 | 14 | markers.start('Page Load'); 15 | await driver.get('https://login.salesforce.com'); 16 | markers.stop('Page Load'); 17 | 18 | markers.start('Login'); 19 | await click(By.id(`username`)); 20 | await typeText(loginEmail, By.id(`username`)); 21 | 22 | await click(By.id(`password`)); 23 | await typeText(credentials.get(credentialName), By.id(`password`)); 24 | 25 | // Click on 'Log In' 26 | await click(By.id(`Login`)); 27 | markers.stop('Login'); 28 | 29 | // I'm not yet sure why the click isn't working, but you can just load the next page 30 | await driver.get("https://expedia.lightning.force.com/lightning/o/Case/list?filterName=Recent"); 31 | 32 | // waits for the page to load 33 | await driver.wait(until.elementIsVisible(driver.findElement(By.css("th.slds-cell-edit > span:nth-child(1)")))); 34 | 35 | await driver.takeScreenshot(); 36 | 37 | // finds a link that 38 | // starts with /lighting/r/5000y00 39 | // (should be consistent across cases, may need to adjust) 40 | // ends with /view 41 | const caseSelector = "a[href^=\"/lightning/r/5000y00\"][href$=\"/view\"]"; 42 | 43 | // Add Marker to Measure Case Load 44 | markers.start('Load Case'); 45 | 46 | 47 | // Click first case in list 48 | await click(By.css(caseSelector)); 49 | 50 | // Wait for page to load 51 | const elementOnNewPage = "div.inFeed:nth-child(3) > div:nth-child(4)"; 52 | await driver.wait(until.elementIsVisible(driver.findElement(By.css(elementOnNewPage)))); 53 | 54 | 55 | // Stop timing case load 56 | markers.stop('Load Case'); 57 | 58 | await driver.takeScreenshot(); 59 | 60 | } 61 | 62 | async function configureDriver() { 63 | return driver.manage().setTimeouts({ 64 | implicit: 10 * 1000 // If an element is not found, reattempt for this many milliseconds 65 | }); 66 | } 67 | 68 | async function click(selector) { 69 | await simulateHumanDelay(); 70 | 71 | const configuredTimeouts = await driver.manage().getTimeouts(); 72 | const clickAttemptEndTime = Date.now() + configuredTimeouts.implicit; 73 | 74 | await reattemptUntil(attemptToClick, clickAttemptEndTime); 75 | 76 | async function attemptToClick() { 77 | await driver.findElement(selector). 78 | click(); 79 | } 80 | } 81 | 82 | 83 | 84 | async function isElementClickable(selector) { 85 | try { 86 | return await driver.findElement(selector).isDisplayed(); 87 | } 88 | catch (error) { 89 | return false; // Will throw an error if element is not connected to the document 90 | } 91 | } 92 | 93 | async function simulateHumanDelay() { 94 | await driver.sleep(550); 95 | } 96 | 97 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 98 | const TIME_BETWEEN_ATTEMPTS = 100; 99 | let numberOfAttempts = 0; 100 | let attemptError; 101 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 102 | try { 103 | numberOfAttempts += 1; 104 | await attemptActionFn(); 105 | } 106 | catch (error) { 107 | attemptError = error; 108 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 109 | continue; // Attempt failed, reattempt 110 | } 111 | attemptError = null; 112 | break; // Attempt succeeded, stop attempting 113 | } 114 | 115 | const wasAttemptSuccessful = !attemptError; 116 | if (!wasAttemptSuccessful) { 117 | throw attemptError; 118 | } 119 | } 120 | 121 | async function typeText(value, selector) { 122 | await simulateHumanDelay(); 123 | await driver.findElement(selector). 124 | sendKeys(value); 125 | } -------------------------------------------------------------------------------- /applications/salesforce/Salesforce_Lightning_login.js: -------------------------------------------------------------------------------- 1 | // Sample Transasction script which logs in to Salesforce Lightning, 2 | // waits for all objects to load, takes a screenshot, and then logs out 3 | 4 | import { By, until } from 'selenium-webdriver'; 5 | import { driver, credentials, markers } from 'thousandeyes'; 6 | 7 | runScript(); 8 | 9 | async function runScript() { 10 | 11 | await configureDriver(); 12 | 13 | // Load the initial login page and take a screenshot 14 | markers.start('Page Load'); 15 | 16 | await driver.get('https://login.salesforce.com/'); 17 | markers.stop('Page Load'); 18 | 19 | await driver.takeScreenshot(); 20 | 21 | // Enter login credentials, with the password coming from the 22 | // ThousandEyes credentials repository 23 | markers.start('Login'); 24 | await click(By.id(`username`)); 25 | 26 | await typeText('fake_username@example.com', By.id(`username`)); 27 | 28 | await click(By.id(`password`)); 29 | 30 | await typeText(credentials.get('fake_username_password'), By.id(`password`)); 31 | 32 | // Click on 'Log In' and wait for the Home page to load 33 | await click(By.id(`Login`)); 34 | 35 | const elements = await driver.findElements(By.css(".slds-card")); 36 | const promises = await elements.map(element => { 37 | return driver.wait(until.elementIsVisible(element)); 38 | }); 39 | await Promise.all(promises); 40 | 41 | markers.stop('Login'); 42 | 43 | await driver.takeScreenshot(); 44 | 45 | // Click on 'Log Out' 46 | markers.start('Logout'); 47 | await click(By.css(".uiImage")); 48 | await driver.wait(until.elementIsVisible(driver.findElement(By.css('[href="/secur/logout.jsp"]')))); 49 | 50 | await click(By.css(`[href="/secur/logout.jsp"]`)); 51 | markers.stop('Logout'); 52 | } 53 | 54 | async function configureDriver() { 55 | await driver.manage().window().setRect({ 56 | width: 1200, 57 | height: 1324 }); 58 | 59 | await driver.manage().setTimeouts({ 60 | implicit: 7 * 1000 // If an element is not found, reattempt for this many milliseconds 61 | }); 62 | } 63 | 64 | 65 | 66 | async function typeText(value, selector) { 67 | await simulateHumanDelay(); 68 | const element = await driver.findElement(selector); 69 | await element.clear(); 70 | await element.sendKeys(value); 71 | } 72 | 73 | async function simulateHumanDelay() { 74 | await driver.sleep(550); 75 | } 76 | 77 | async function click(selector) { 78 | await simulateHumanDelay(); 79 | 80 | const configuredTimeouts = await driver.manage().getTimeouts(); 81 | const clickAttemptEndTime = Date.now() + configuredTimeouts.implicit; 82 | 83 | await reattemptUntil(attemptToClick, clickAttemptEndTime); 84 | 85 | async function attemptToClick() { 86 | await driver.findElement(selector). 87 | click(); 88 | } 89 | } 90 | 91 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 92 | const TIME_BETWEEN_ATTEMPTS = 100; 93 | let numberOfAttempts = 0; 94 | let attemptError; 95 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 96 | try { 97 | numberOfAttempts += 1; 98 | await attemptActionFn(); 99 | } 100 | catch (error) { 101 | attemptError = error; 102 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 103 | continue; // Attempt failed, reattempt 104 | } 105 | attemptError = null; 106 | break; // Attempt succeeded, stop attempting 107 | } 108 | 109 | const wasAttemptSuccessful = !attemptError; 110 | if (!wasAttemptSuccessful) { 111 | throw attemptError; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /applications/salesforce/placeholder_file: -------------------------------------------------------------------------------- 1 | Hi! Please delete me once this folder is populated. 2 | -------------------------------------------------------------------------------- /examples/OAuthProtectedRestAPI.js: -------------------------------------------------------------------------------- 1 | /* 2 | This script example authenticates with the OAuth 2.0 service 3 | then uses the OAuth access token to query the REST API. 4 | Script validates REST API response contents and errors on unexpected 5 | values. It also exposes certain response values as markers and 6 | displays response contents in a screenshot. 7 | 8 | Authors: primoz@thousandeyes.com, benton@thousandeyes.com 9 | */ 10 | import { 11 | By 12 | } from 'selenium-webdriver'; 13 | import { 14 | driver, 15 | markers, 16 | credentials 17 | } from 'thousandeyes'; 18 | import fetch from 'node-fetch'; 19 | 20 | const clientId = 'ThousandEyesClient'; 21 | const clientSecret = credentials.get('OAuth_DCHS'); 22 | const oauthUrl = 'https://oauthservice.example.com/oauth2/api/v1/token?token_format=jwt&grant_type=client_credentials'; 23 | const apiUrl = 'https://restapi.example.com/api/v1/health'; 24 | 25 | runScript(); 26 | 27 | async function runScript() { 28 | 29 | /* 30 | Fetch the Access token from the OAuth service 31 | */ 32 | await markers.start('OAuth Query Time'); 33 | const buffer = Buffer.from(clientId + ':' + clientSecret); 34 | const basicHash = buffer.toString("base64"); 35 | const oauthRequest = { 36 | method: 'POST', 37 | /* clientId and clientSecret are provided with Basic Authorization 38 | but could also be provided as POST variables */ 39 | headers: { 40 | 'Authorization': 'Basic ' + basicHash 41 | } 42 | } 43 | const oauthResponse = await fetch(oauthUrl, oauthRequest); 44 | if (!oauthResponse.ok) { 45 | const oauthErrorResponseText = await oauthResponse.text(); 46 | throw new Error('OAuth HTTP ' + oauthResponse.status + '(' + oauthResponse.statusText + ')\n' + oauthErrorResponseText); 47 | } 48 | const oauthResponseJson = await oauthResponse.json(); 49 | if (!('access_token' in oauthResponseJson)) { 50 | const oauthResponseText = JSON.stringify(oauthResponseJson, null, 4); 51 | throw new Error('OAuth response is missing access_token:\n' + oauthResponseText); 52 | } 53 | const accessToken = oauthResponseJson.access_token; 54 | await markers.stop('OAuth Query Time'); 55 | 56 | 57 | /* 58 | Query the API 59 | */ 60 | await markers.start('API Query Time'); 61 | const apiRequest = { 62 | method: 'GET', 63 | headers: { 64 | 'Authorization': 'Bearer ' + accessToken 65 | } 66 | } 67 | const apiResponse = await fetch(apiUrl, apiRequest); 68 | if (!apiResponse.ok) { 69 | throw new Error('API HTTP ' + apiResponse.status + '(' + apiResponse.statusText + ')'); 70 | } 71 | const apiResponseJson = await apiResponse.json(); 72 | await markers.stop('API Query Time'); 73 | 74 | /* 75 | Put the complete API response into a screenshot 76 | */ 77 | const apiResponseText = JSON.stringify(apiResponseJson, null, 4); 78 | await driver.executeScript(`document.getElementsByTagName('body')[0].remove();document.write('');`) 79 | var textArea = await driver.findElement(By.id('output')); 80 | await textArea.sendKeys(apiResponseText); 81 | await driver.takeScreenshot(); 82 | 83 | 84 | /* 85 | Validate the API output 86 | If a property is not 'ok', add it to error message and throw an error. 87 | */ 88 | if (!('globalChecks' in apiResponseJson)) { 89 | throw new Error('API response JSON is missing \'globalChecks\''); 90 | } 91 | 92 | let errorMessage = ''; 93 | for (let property in apiResponseJson.globalChecks) { 94 | markers.start(property + ' status: ' + apiResponseJson.globalChecks[property].status); 95 | markers.stop(property + ' status: ' + apiResponseJson.globalChecks[property].status); 96 | if (apiResponseJson.globalChecks[property].status != 'ok') { 97 | errorMessage += property + ' status: ' + apiResponseJson.globalChecks[property].status + '\n'; 98 | } 99 | } 100 | if (errorMessage.length > 0) { 101 | throw new Error(errorMessage); 102 | } 103 | 104 | }; 105 | -------------------------------------------------------------------------------- /examples/assertCondition.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | import assert from 'assert'; 4 | 5 | runScript(); 6 | 7 | async function runScript() { 8 | 9 | await driver.get('https://www.x-rates.com/table/?from=USD&amount=1'); 10 | 11 | const usdToEuroRateCell = await driver.findElement(By.css(`[href="https://www.x-rates.com/graph/?from=USD&to=EUR"]`)); 12 | const currentUsdToEuroRate = Number(usdToEuroRateCell.getText()); 13 | 14 | const isOneEuroWorthMoreThanTwoUsd = currentUsdToEuroRate < 0.5; 15 | assert(isOneEuroWorthMoreThanTwoUsd, 'One Euro is not worth more than 2 USD!'); 16 | } 17 | -------------------------------------------------------------------------------- /examples/checkIfElementExists.js: -------------------------------------------------------------------------------- 1 | import { By, Key } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | import assert from 'assert'; 4 | 5 | runScript(); 6 | 7 | async function runScript() { 8 | // Randomly create either 9 | // 10 | // OR 11 | // 12 | const customElementTagToAppend = (Math.random() < 0.5) ? 'custom-element-one' : 'custom-element-two'; 13 | await driver.executeScript(`document.body.append(document.createElement('${customElementTagToAppend}'))`) 14 | 15 | // Check if element one exists; if not, throw an error 16 | const doesElementOneExist = await doesElementExist(By.css('custom-element-one')); 17 | assert(doesElementOneExist, 'Element one does not exist!'); 18 | } 19 | 20 | async function doesElementExist(selector) { 21 | return (await driver.findElements(selector)).length > 0;; 22 | } 23 | -------------------------------------------------------------------------------- /examples/clickSpecificPosition.js: -------------------------------------------------------------------------------- 1 | import { By, Origin } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | await driver.get('https://google.com'); 8 | const searchBar = driver.findElement(By.name('q')); 9 | 10 | // Clicks the google search bar, expanding suggestions. 11 | // Then clicks one of the suggested items 12 | await driver.actions({ bridge: true }) 13 | .move({ x: 0, y: 0, origin: searchBar }) 14 | .click() 15 | .pause(500) 16 | .move({ x: 0, y: 100, origin: Origin.POINTER }) 17 | .click() 18 | .perform(); 19 | 20 | await driver.sleep(1000); 21 | }; 22 | -------------------------------------------------------------------------------- /examples/closeConditionalPopup.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | 8 | // This page will show a popup 50% of the time 9 | await driver.get('http://jam.si/popup.php'); 10 | 11 | await driver.sleep(1 * 1000); 12 | 13 | const doesPopupExist = await doesElementExist(By.id('popup')); 14 | if (doesPopupExist) { 15 | const closeButton = await driver.findElement(By.css('[type="button"]')); 16 | closeButton.click(); 17 | } 18 | 19 | await driver.sleep(1 * 1000); 20 | } 21 | 22 | async function doesElementExist(selector) { 23 | return (await driver.findElements(selector)).length > 0;; 24 | } 25 | -------------------------------------------------------------------------------- /examples/closeRandomPopupAsync.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | await driver.manage().setTimeouts({ 8 | implicit: 5 * 1000, // If an element is not found, reattempt for this many milliseconds 9 | }); 10 | await driver.get('http://jam.si/popup.php'); 11 | 12 | // Since the communication between the Transaction execution context and the 13 | // Browser is synchronous and thus every request is blocking the next one,, 14 | // every invocation of driver.findElement() must return before the next one 15 | // starts being processed. This means that we effectively cannot wait on two 16 | // elements at once in an asynchronous/reactive fashion, even if the transaction 17 | // execution context supports asynchronous programming. Consequentially, if we'd 18 | // create an async method that is waiting for a random popup to appear, and 19 | // that popup never appears, the next step (the regular transaction flow after 20 | // the popup closing function definition) will never get executed. 21 | // 22 | // What we can do to avoid this condition, however, is to "deploy" a custom 23 | // JavaScript code into browser/page context. That code will run independently 24 | // of the transaction code, and will close the random popup if it appears. 25 | // 26 | // To set this up, a single call to the browser is required. Although the 27 | // driver.executeScript() call itself is blocking in the same fashion the 28 | // driver.findElement() is, it returns immediately after the non-async part 29 | // of the code has completed. If this code sets itself up to be re-run in the 30 | // browser later (using the window.setTimeout() method), that does not affect 31 | // the communication between the browser and the transaction execution context 32 | // anymore. 33 | // 34 | // It is important to pay attention to where/when this independent JavaScript 35 | // code is deployed. It must end up in the correct "webpage context". If the 36 | // page is changed (or reloaded), this code will vanish. 37 | console.log("Let's set up the popup-closing function inside the browser."); 38 | await driver.executeScript(` 39 | function closePopup() { 40 | 41 | // Try to find a visible popup close button 42 | // It's important for this Xpath to not match a hidden popup, 43 | // otherwise the closing attempt will happen _before_ the popup becomes visible 44 | var popupCloseButton = document.evaluate('//div[@id="popup" and not(contains(@style, "hidden"))]/button[.="Close me"]', document, null, XPathResult.ANY_TYPE, null).iterateNext(); 45 | 46 | if (popupCloseButton) { 47 | // When found, click it to close the popup and we're done 48 | console.log("Found the visible popup, closing it"); 49 | popupCloseButton.click(); 50 | } else { 51 | // When not (yet) found, let's reschedule this function to run after 1 second again 52 | console.log("Visible popup not found, scheduling a re-test in 1s"); 53 | setTimeout(closePopup, 1000) 54 | } 55 | } 56 | 57 | // Run the function once, to set up the loop 58 | closePopup(); 59 | `); 60 | console.log("Popup-closing function has been set up and started."); 61 | 62 | // Click on 'Click this link if you can!' 63 | // 64 | // If the link is hidden behind the popup, this command is blocking the 65 | // transaction until the link becomes visible. 66 | // 67 | // However, the popup-closing code we "deployed" with the driver.executeScript() 68 | // earlier is now running inside the browser independently. The transaction will 69 | // correctly continue when the popup is dismissed or when the popup does not 70 | // appear at all. 71 | await driver.findElement(By.xpath(`//a[text()="Click this link if you can!"]`)).click(); 72 | console.log("All done."); 73 | } 74 | -------------------------------------------------------------------------------- /examples/configuringImplicitWaits.js: -------------------------------------------------------------------------------- 1 | import { driver } from 'thousandeyes'; 2 | 3 | runScript(); 4 | 5 | async function runScript() { 6 | // Doing some configuration before the start of the actual transaction is a common pattern. 7 | // If you want to exclude the configuration time from the recorded transaction timings, use `transaction.start` 8 | // to denote the start time of the transaction 9 | await configureDriver(); 10 | } 11 | 12 | async function configureDriver() { 13 | /* 14 | `driver.manage().setTimeouts` allows you to define the timeouts for various operations. 15 | One common one is to set an "implicit" wait time. The "implicit" wait time means that 16 | driver will wait for that many milliseconds when trying to find an element before considering 17 | the operation failed. 18 | 19 | This can often be useful as sometimes an element won't exist right away in the browser due to 20 | animations or load times prior to the element appearing. 21 | 22 | Alternatively you can use explicit waits. 23 | */ 24 | return driver.manage().setTimeouts({ 25 | implicit: 5 * 1000 // If an element is not found, reattempt for this many milliseconds 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /examples/customTransactionStartTime.js: -------------------------------------------------------------------------------- 1 | import { driver, transaction } from 'thousandeyes'; 2 | 3 | runScript(); 4 | 5 | async function runScript() { 6 | 7 | await driver.get('https://google.com'); 8 | 9 | // This will set the start time of the transaction to the point that start is called. 10 | // This can be useful if you want to do some configuration or authenticate before recording data 11 | // Note that this will affect the start time of markers set via `markers.set` 12 | await transaction.start(); 13 | } 14 | -------------------------------------------------------------------------------- /examples/detectJsErrors.js: -------------------------------------------------------------------------------- 1 | import { logging } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | import assert from 'assert'; 4 | 5 | runScript(); 6 | 7 | async function runScript() { 8 | 9 | // Ensure we're not pulling in logs from prior pages/tests 10 | // Calling this function will clear out the logs up to the point 11 | await driver.manage().logs().get('browser'); 12 | 13 | await driver.get('https://wikipedia.org'); 14 | 15 | // Throw an error on a delay to simulate an uncaught JS error 16 | await driver.executeScript(`setTimeout(() => { throw new Error('Simulate Uncaught Error') }, 0)`); 17 | const browserLogs = await driver.manage().logs().get('browser'); 18 | 19 | 20 | console.log('Browser logs:'); 21 | console.log(JSON.stringify(browserLogs, null, ' '), '\n'); 22 | 23 | const severeErrors = browserLogs.filter(logEntry => logEntry.level === logging.Level.SEVERE); 24 | assert(severeErrors.length === 0, 'Errors found during the page load!') 25 | } 26 | -------------------------------------------------------------------------------- /examples/dismissBrowserNativeAlert.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | await driver.get('about:blank'); 8 | 9 | await driver.executeScript(`alert("I'm a browser native alert!")`) 10 | 11 | await driver.sleep(2 * 1000); 12 | await driver.switchTo().alert().dismiss(); 13 | await driver.sleep(1 * 1000); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/duoApiAuth.js: -------------------------------------------------------------------------------- 1 | /* 2 | This script shows an example of how to use the 'node-fetch' and 'crypto' modules 3 | to perform token based authnetication with Cisco Duo REST API. It also uses the 4 | 'assert' module to validate the API response. 5 | */ 6 | 7 | import { markers, credentials } from 'thousandeyes'; 8 | import assert from 'assert'; 9 | import { createHmac } from 'crypto'; 10 | import fetch from 'node-fetch'; 11 | 12 | runScript(); 13 | 14 | async function runScript() { 15 | // TODO: Create a ThousandEyes credential called "DuoIntegrationKey" and set it to your Duo Integration Key (this is your public key) 16 | const integrationKey = credentials.get('DuoIntegrationKey'); 17 | 18 | // TODO: Create a ThousandEyes credential called "DuoSecretKey" and set it to your Duo Secret Key (this is your private key) 19 | const secretKey = credentials.get('DuoSecretKey'); 20 | 21 | // TODO: Create a ThousandEyes credential called "DuoUserId" and set it to the User ID that will be used to access Duo API 22 | const userId = credentials.get('DuoUserId'); 23 | 24 | // TODO: get the API endpoint from Duo for the app you created; ex. api-4f26128f.duosecurity.com 25 | const host = 'api-4f26128f.duosecurity.com'; 26 | 27 | const protocol = "https"; 28 | const method = 'POST'; 29 | const path = '/auth/v2/preauth'; 30 | const body = `username=${userId}`; 31 | const url = `${protocol}://${host}${path}`; 32 | const dateString = getRFC2822Date(); 33 | const payload2Hash = [dateString, method, host, path, body].join("\n"); 34 | 35 | // Create the authentication code secured with the Secret Key 36 | const hash = createHmac('sha1', secretKey) 37 | .update(payload2Hash) 38 | .digest('hex'); 39 | 40 | // Create the request body, using the Integration Key and HMAC authentication code as the Basic auth contents 41 | const requestBody = { 42 | method, 43 | body, 44 | headers: { 45 | 'Content-Type': 'application/x-www-form-urlencoded', 46 | 'Authorization': `Basic ${to64(`${integrationKey}:${hash}`)}`, 47 | 'Date': dateString, 48 | } 49 | }; 50 | 51 | markers.start('auth'); 52 | const response = await fetch(url, requestBody); 53 | const respJSON = await response.json(); 54 | markers.stop('auth'); 55 | 56 | assert(respJSON.stat == "OK", "API auth failed"); 57 | } 58 | 59 | function getRFC2822Date() { 60 | return new Date().toUTCString().replace(/GMT/g, '+0000'); 61 | } 62 | 63 | function to64(data) { 64 | return Buffer.from(data).toString('base64'); 65 | } -------------------------------------------------------------------------------- /examples/enterBasicAuthCredentials.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | await driver.get('https://jigsaw.w3.org/HTTP/'); 8 | 9 | await driver.sleep(1 * 1000); 10 | 11 | // Click link that opens basic auth prompt 12 | // await driver.findElement(By.css('[href="Basic/"]')).click(); 13 | 14 | // Instead, navigate directly with basic auth credentials in url 15 | // (https://username:password@url) 16 | await driver.get('https://guest:guest@jigsaw.w3.org/HTTP/Basic/'); 17 | 18 | await driver.sleep(1 * 1000); 19 | }; 20 | -------------------------------------------------------------------------------- /examples/fetchAPIWithBasicAuth.js: -------------------------------------------------------------------------------- 1 | /* 2 | A script that uses fetch() and basic HTTP authentication to fetch data from API 3 | Author: primoz@thousandeyes.com 4 | */ 5 | 6 | import { By, Key, until } from 'selenium-webdriver'; 7 | import fetch from 'node-fetch'; 8 | import assert from 'assert'; 9 | import { credentials, markers } from 'thousandeyes' 10 | 11 | runScript(); 12 | 13 | async function runScript() { 14 | 15 | // replace the username string with your ThousandEyes login 16 | const username = 'user@example.com'; 17 | 18 | // retrieve the Basic authentication token from the credential stored as 'TE-Basic-token' 19 | // in the ThousandEyes Credentials Repository 20 | const password = credentials.get('TE-Basic-token'); 21 | 22 | // Encode credentials in base64 23 | let buffer = Buffer.from(username+':'+password); 24 | let apiToken = buffer.toString('base64'); 25 | 26 | let requestBody = { 27 | method: 'GET', 28 | headers: { 29 | 'Authorization': 'Basic ' + apiToken 30 | } 31 | } 32 | 33 | markers.start('fetch'); 34 | let response = await fetch('https://api.thousandeyes.com/v6/tests.json', requestBody); 35 | markers.stop('fetch'); 36 | 37 | if (!response.ok) { 38 | assert.fail(response.status + ' ' + response.statusText); 39 | } 40 | let responseText = await response.json(); 41 | console.log(responseText); 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /examples/findAnyElement.js: -------------------------------------------------------------------------------- 1 | import { By, Key, until } from 'selenium-webdriver'; 2 | import { driver, markers, transaction } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | 8 | // Open the page 9 | await driver.get(`http://tmp.skufca.si/te/trx-branching/`); 10 | 11 | // Let's find any one of these elements, whatever appears first 12 | let locators = { 13 | "locator-1": By.xpath(`//div[@id="mydiv-1"]`), 14 | "locator-2": By.xpath(`//div[@id="mydiv-2"]`), 15 | "locator-3": By.xpath(`//div[@id="mydiv-3"]`), 16 | }; 17 | let [locatorName, element] = await findAnyElement(locators, 5000); 18 | 19 | // Evaluate what has been found 20 | if (locatorName == "locator-1") { 21 | markers.set("Element #1 found"); 22 | } else { 23 | markers.set("Element #2 or #3 found"); 24 | } 25 | } 26 | 27 | async function findAnyElement(locators, timeoutInMs) { 28 | const TIME_BETWEEN_ATTEMPTS_MS = 100; 29 | let attemptEndTime = Date.now() + timeoutInMs; 30 | 31 | // Store the current timeout settings, then disable the implicit 32 | // timeout, since we're doing the polling ourselves. 33 | let origTimeouts = await driver.manage().getTimeouts(); 34 | await driver.manage().setTimeouts({ 35 | implicit: 0 36 | }); 37 | 38 | while (Date.now() < attemptEndTime) { 39 | for (var locatorName in locators) { 40 | let locator = locators[locatorName]; 41 | let elementsFound = await driver.findElements(locator); 42 | 43 | if (elementsFound.length > 0) { 44 | let element = elementsFound[0]; 45 | await driver.manage().setTimeouts(origTimeouts); 46 | return [locatorName, element]; 47 | } 48 | } 49 | await driver.sleep(TIME_BETWEEN_ATTEMPTS_MS); 50 | } 51 | 52 | await driver.manage().setTimeouts(origTimeouts); 53 | throw new Error(`None of the locators matched anything so far and the timeout has been reached.`); 54 | } 55 | -------------------------------------------------------------------------------- /examples/html5CanvasInteractionCoordinates.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 canvas child elements are not part of DOM, hence Selenium Webdriver cannot directly access them. 3 | A possible shortcut to interact with elements in the canvas is to click on a certain predefined coordinate in the canvas itself. 4 | This example clicks at three different locations in the canvas and captures the explosion created at the click location. 5 | 6 | Author: primoz@thousandeyes.com 7 | 8 | */ 9 | 10 | import { By, Key, Origin } from 'selenium-webdriver'; 11 | import { driver, test } from 'thousandeyes'; 12 | 13 | runScript(); 14 | 15 | async function runScript() { 16 | 17 | await configureDriver(); 18 | 19 | const settings = test.getSettings(); 20 | 21 | // Load page 22 | await driver.get(settings.url); 23 | 24 | await driver.switchTo().frame(driver.findElement(By.id(`result`))) 25 | 26 | const canvas = await driver.findElement(By.id(`c`)); 27 | 28 | await driver.sleep(500); 29 | 30 | await driver.actions({ bridge: true }) 31 | .move({ x: -200, y: -200, origin: canvas }) 32 | .click() 33 | .perform(); 34 | await driver.sleep(100); 35 | await driver.takeScreenshot(); 36 | 37 | await driver.actions({ bridge: true }) 38 | .move({ x: 200, y: 100, origin: Origin.POINTER }) 39 | .click() 40 | .perform(); 41 | await driver.sleep(100); 42 | await driver.takeScreenshot(); 43 | 44 | await driver.actions({ bridge: true }) 45 | .move({ x: 200, y: 100, origin: Origin.POINTER }) 46 | .click() 47 | .perform(); 48 | await driver.sleep(100); 49 | await driver.takeScreenshot(); 50 | 51 | 52 | } 53 | 54 | async function configureDriver() { 55 | await driver.manage().setTimeouts({ 56 | implicit: 7 * 1000, // If an element is not found, reattempt for this many milliseconds 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /examples/httpRequestConnectProxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | A script that establishes a connection with a CONNECT proxy server and sends subsequent requests through the proxy 3 | to the target server. Supports HTTP and HTTPS URLs and upgrades connection to TLS for HTTPS URLs. 4 | Author: primoz@thousandeyes.com 5 | */ 6 | 7 | import { By, Key } from 'selenium-webdriver'; 8 | import { driver } from 'thousandeyes'; 9 | import assert from 'assert'; 10 | import net from 'net'; 11 | import tls from 'tls'; 12 | 13 | let proxyHost = '10.10.10.110'; 14 | let proxyPort = 8888; 15 | let url = 'https://www.google.com/'; 16 | 17 | 18 | 19 | runScript(); 20 | 21 | async function runScript() { 22 | 23 | let sock; 24 | try { 25 | sock = await connectProxy(proxyHost, proxyPort, url); 26 | } catch (e) { 27 | assert.fail('Proxy error: ' + e); 28 | } 29 | 30 | let response; 31 | try { 32 | response = await fetchUsingProxy(sock, url); 33 | } catch (e) { 34 | assert.fail('Fetch error: ' + e); 35 | } 36 | 37 | if (!response.match(/HTTP\/.*200/g)) { 38 | assert.fail(response); 39 | } 40 | 41 | console.log(response); 42 | 43 | } 44 | 45 | 46 | 47 | function connectProxy(proxyHost, proxyPort, url) { 48 | 49 | let urlComponents = urlToComponents(url); 50 | 51 | return new Promise(function(resolve, reject) { 52 | let options = { 53 | host: proxyHost, 54 | port: proxyPort 55 | } 56 | let sock = net.connect(options, () => { 57 | sock.write('CONNECT ' + urlComponents['serverWPort'] + ' HTTP/1.1\r\nHost: ' + urlComponents['server'] + '\r\n\r\n', null, (err) => { 58 | if (err !== undefined) { 59 | reject(err); 60 | } 61 | }) 62 | }); 63 | sock.once('close', () => { 64 | reject('Proxy connection closed.'); 65 | }); 66 | 67 | sock.once('error', (err) => { 68 | reject(err); 69 | }); 70 | 71 | sock.once('data', (data) => { 72 | if (data.toString('utf8').match(/HTTP\/.*200/g)) { 73 | // If target server is HTTPS, upgrade current sock to TLS. After we issued CONNECT command to the proxy, proxy now becomes transparent, 74 | // so TLS handshake is actually done between the client and the server. 75 | if (urlComponents['https']) { 76 | let sockTls = tls.connect({ 77 | socket: sock, 78 | servername: urlComponents['server'] 79 | }, () => { 80 | resolve(sockTls); 81 | }); 82 | sockTls.once('close', () => { 83 | reject('Proxy or server connection closed.'); 84 | }); 85 | sockTls.once('error', (err) => { 86 | reject(err); 87 | }); 88 | } else { 89 | resolve(sock); 90 | } 91 | } else { 92 | reject(data.toString('utf8')); 93 | } 94 | }); 95 | }) 96 | } 97 | 98 | function fetchUsingProxy(sock, url, headers = {}, requestType = 'GET', body = '') { 99 | 100 | let urlComponents = urlToComponents(url); 101 | 102 | let headersStr = ''; 103 | for (const [key, value] of Object.entries(headers)) { 104 | headersStr += key + ': ' + value + '\r\n'; 105 | } 106 | 107 | let payload = ` 108 | ` + requestType + ` ` + urlComponents['relativeUrl'] + ` HTTP/1.1 109 | Host: ` + urlComponents['server'] + ` 110 | Content-Length: ` + body.trim().replace(/(?:\r\n|\r|\n) */g, '').length + ` 111 | ` + headersStr + ` 112 | ` + body.trim().replace(/(?:\r\n|\r|\n) */g, '') + ` 113 | ` 114 | payload = payload.trim().replace(/(?:\r\n|\r|\n)/g, '\r\n').replace(/ +/g, ' ').replace(/\r\n +/g, '\r\n'); 115 | 116 | return new Promise(function(resolve, reject) { 117 | 118 | sock.write(payload + '\r\n\r\n', null, (err) => { 119 | if (err !== undefined) { 120 | reject(err); 121 | } 122 | }) 123 | sock.once('close', () => { 124 | reject('Proxy or server connection closed.'); 125 | }); 126 | sock.once('error', (err) => { 127 | reject(err); 128 | }); 129 | sock.once('data', (data) => { 130 | if (data.toString('utf8').match(/HTTP\/.*200/g)) { 131 | resolve(data.toString('utf8')); 132 | } else { 133 | reject(data.toString('utf8')); 134 | } 135 | }); 136 | }) 137 | } 138 | 139 | function urlToComponents(url) { 140 | let regex = /(https?):\/\/([\w\.:]+)(\/.*)?/ 141 | var match = regex.exec(url); 142 | if (!match || match.length < 4) { 143 | throw ('Invalid URL.') 144 | } 145 | let isHttps = false; 146 | if (match[1] == 'https') { 147 | isHttps = true; 148 | } 149 | let serverHost = match[2] 150 | if (serverHost === undefined) { 151 | throw ('Invalid URL.') 152 | } 153 | let relativeUrl = match[3] 154 | if (relativeUrl === undefined) { 155 | relativeUrl = '/'; 156 | } 157 | let serverHostPort = serverHost 158 | if (!serverHost.includes(":")) { 159 | if (isHttps) { 160 | serverHostPort = serverHost + ':443'; 161 | } 162 | } 163 | return { 164 | 'https': isHttps, 165 | 'server': serverHost, 166 | 'serverWPort': serverHostPort, 167 | 'relativeUrl': relativeUrl 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /examples/iframe.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | // The page at thousandeyes.com/outages contains an iframe 8 | await driver.get('https://thousandeyes.com/outages'); 9 | 10 | // To interact with elements within the iframe, must switch the web driver context 11 | // to the iframe by using the `driver.switchTo().frame()` API. 12 | await driver.switchTo().frame(driver.findElement(By.css(`.embed-container > iframe`))) 13 | 14 | // You can then locate elements within the iframe as you normally would 15 | await driver.findElement(By.css(`.point-container:nth-child(1) > .point`)).click(); 16 | 17 | // To switch the driver back to the default context, use: 18 | await driver.switchTo().defaultContent(); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /examples/imapLoginAndFetchEmail.js: -------------------------------------------------------------------------------- 1 | // 2 | // ThousandEyes transaction script which demonstrates connecting to an IMAP 3 | // e-mail server secured with TLS, and fetching a message from the Inbox 4 | // 5 | 6 | import { markers, net, credentials, driver } from 'thousandeyes'; 7 | import assert from 'assert'; 8 | 9 | runCode(); 10 | 11 | async function runCode() { 12 | const host = 'box.the-acme-corporation.net'; 13 | const port = 993; 14 | const username = 'tonystark@the-acme-corporation.net'; 15 | const password = credentials.get(username); 16 | 17 | // List of IMAP commands to send to the server, with associated marker names 18 | // and content-validation substrings 19 | const steps = [ 20 | // IMAP Command Marker Name Response validation string 21 | [`? LOGIN ${username} ${password}\n`, 'Login', '? OK'], 22 | [`? SELECT Inbox\n`, 'Select Inbox', '? OK'], 23 | [`? FETCH 1:1 RFC822\n`, 'Fetch E-mail', `Delivered-To: ${username}`], 24 | ] 25 | 26 | markers.start('Connect'); 27 | // Connect to the IMAP Server 28 | const socket = await net.connectTls(port, host, { 29 | minVersion: 'TLSv1.2', 30 | }); 31 | markers.stop('Connect'); 32 | await socket.setEncoding('utf8'); 33 | 34 | // Read the Message of the Day from the Server 35 | console.log(await socket.read()); // Remove this line if there is no MOTD 36 | 37 | // Send and validate all the commmands from the list above 38 | await imapSendCommandAndValidateResponse(socket, steps); 39 | } 40 | 41 | async function imapSendCommandAndValidateResponse(socket, commandsList){ 42 | 43 | for(let i=0; i < commandsList.length; i++){ 44 | const [command, markerName, validationString] = commandsList[i]; 45 | 46 | // Start a marker for this command 47 | markers.start(markerName); 48 | 49 | // Send the command to the server 50 | await socket.writeAll(command); 51 | console.log(command); // (Optional, used when running in the Recorder IDE) 52 | 53 | // Read the response from the server 54 | let response = ""; 55 | while(!response.endsWith('\r\n')){ 56 | response += await socket.read(); 57 | } 58 | console.log(response); // (Optional, used when running in the Recorder IDE) 59 | 60 | // Check for the validation substring in the response, raising an error if not valid 61 | assert( 62 | response.includes(validationString), 63 | `${validationString} not found in response: ${response}` 64 | ); 65 | 66 | // Stop the marker for this command 67 | markers.stop(markerName); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/loadPage.js: -------------------------------------------------------------------------------- 1 | import { driver } from 'thousandeyes'; 2 | 3 | runScript(); 4 | 5 | async function runScript() { 6 | // Opens google.com; Make sure to add `await` to any async operations to wait for the operation to finish 7 | // If you're not sure that an operation is async, check the returned type. 8 | // Anything that returns a "Promise" is an async operation 9 | await driver.get('https://google.com'); 10 | } 11 | -------------------------------------------------------------------------------- /examples/moveMouseIntoElement.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | await driver.get('https://www.tiffany.com'); 8 | const element = driver.findElement(By.css('body > div.page-wrap > header > div.header__nav-container.stick > nav > ul > li:nth-child(1) > a')); 9 | await moveMouseInto(element); 10 | await driver.sleep(1000) 11 | }; 12 | 13 | async function moveMouseInto(element) { 14 | await driver.actions({ bridge: true }) 15 | .move({ x: -1, y: 0, origin: element }) 16 | .move({ x: 1, y: 0, origin: element }) 17 | .perform(); 18 | } -------------------------------------------------------------------------------- /examples/reattemptClickUntilOtherElementExists.js: -------------------------------------------------------------------------------- 1 | /* 2 | When a JavaScript-heavy website assigns actions to buttons and other elements after the page load event, 3 | traditional await click() step will click the button before the button has the appropriate action. 4 | 5 | This example implements a function that keeps clicking the button until the element loaded by the button 6 | action (i.e. an element on a new page that opens after the button click) exists. 7 | 8 | Author: primoz@thousandeyes.com 9 | */ 10 | 11 | import { By, Key } from 'selenium-webdriver'; 12 | import { driver, credentials, markers } from 'thousandeyes'; 13 | 14 | runScript(); 15 | 16 | async function runScript() { 17 | 18 | await configureDriver(); 19 | 20 | // Load page 21 | await driver.get('https://cisco.com'); 22 | 23 | // Keep clicking 'Products' every 100ms until 'cdc-nav' element is present. 24 | await reattemptClickUntilOtherElementExists(By.xpath(`//a[text()='Meraki']`), By.xpath(`//title[contains(text(), 'Cisco Meraki')]`)); 25 | 26 | await driver.takeScreenshot(); 27 | 28 | } 29 | 30 | async function reattemptClickUntilOtherElementExists(clickSelector, existSelector) { 31 | let configuredTimeouts = await driver.manage().getTimeouts(); 32 | let implicitTimeout = configuredTimeouts.implicit; 33 | let attemptEndTime = Date.now() + implicitTimeout; 34 | await driver.manage().setTimeouts({ implicit: 100 }); 35 | let clicked = false; 36 | let lastError; 37 | while (Date.now() < attemptEndTime) { 38 | try { 39 | let e = await driver.findElement(clickSelector); 40 | clicked = true; 41 | await e.click(); 42 | } catch (e) { 43 | if (!clicked) { 44 | lastError = e; 45 | } 46 | } 47 | try { 48 | await driver.findElement(existSelector); 49 | await driver.manage().setTimeouts({ implicit: implicitTimeout }); 50 | return; 51 | } catch (e) { 52 | if (clicked) { 53 | lastError = e; 54 | } 55 | } 56 | } 57 | await driver.manage().setTimeouts({ implicit: implicitTimeout }); 58 | throw lastError; 59 | } 60 | 61 | async function configureDriver() { 62 | await driver.manage().setTimeouts({ 63 | implicit: 7 * 1000, // If an element is not found, reattempt for this many milliseconds 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /examples/repeatingError.js: -------------------------------------------------------------------------------- 1 | /* 2 | Sometimes you just need to test your alerting workflow with a test that throws out 3 | errors in a predictable way. This is it. 4 | 5 | Author: primoz@thousandeyes.com 6 | */ 7 | 8 | import { test } from 'thousandeyes'; 9 | 10 | const errorRounds = 1; 11 | const errorOutOfRounds = 2; 12 | 13 | runScript(); 14 | 15 | async function runScript() { 16 | 17 | const settings = test.getSettings(); 18 | let interval = settings.interval 19 | // Interval is NaN in IDE or instant test. 20 | if (isNaN(interval)) { 21 | interval = 300; 22 | } 23 | 24 | let timestamp = Math.floor(Date.now() / 1000); 25 | let roundNo = Math.floor(timestamp / interval); 26 | let currentRound = roundNo % errorOutOfRounds; 27 | 28 | console.log('Round ' + (currentRound+1) + ' of ' + errorOutOfRounds); 29 | if (currentRound < errorRounds) { 30 | console.log('Throw error'); 31 | throw Error ('This error is thrown for ' + errorRounds + ' rounds every ' + errorOutOfRounds + ' rounds.'); 32 | } 33 | 34 | }; 35 | -------------------------------------------------------------------------------- /examples/scrollElementIntoView.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | 8 | // Load page 9 | await driver.get('http://the-acme-corporation.com/longpage.html'); 10 | 11 | let elem = await driver.findElement(By.xpath(`//h3`)); 12 | 13 | await scrollElementIntoView(elem, {block: 'end'}) 14 | await driver.takeScreenshot(); 15 | 16 | await scrollElementIntoView(elem, {block: 'center'}) 17 | await driver.takeScreenshot(); 18 | 19 | await scrollElementIntoView(elem, {block: 'start'}) 20 | await driver.takeScreenshot(); 21 | 22 | }; 23 | 24 | async function scrollElementIntoView(element, scrollIntoViewOptions){ 25 | // For details on the `scrollIntoViewOptions` parameter, see: 26 | // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView 27 | await driver.executeScript( 28 | `arguments[0].scrollIntoView(arguments[1]);`, 29 | element, scrollIntoViewOptions 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /examples/shadowDomFindShadowRoot.js: -------------------------------------------------------------------------------- 1 | /* 2 | Shadow DOM enables you to attach a DOM tree to an element, and have 3 | the internals of this tree hidden from JavaScript and CSS running in 4 | the page. 5 | 6 | https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM 7 | 8 | This script is an example of how you can select the Shadow root to 9 | find and interact with elements withing the Shadow tree. 10 | 11 | Only CSS selectors work within the Shadow tree, do not use Xpath! 12 | 13 | Author: primoz@thousandeyes.com 14 | */ 15 | 16 | import { By, Key } from 'selenium-webdriver'; 17 | import { driver, test } from 'thousandeyes'; 18 | 19 | runScript(); 20 | 21 | async function runScript() { 22 | 23 | await configureDriver(); 24 | 25 | const settings = test.getSettings(); 26 | 27 | // Load page 28 | await driver.get(settings.url); 29 | 30 | let shadowRoot1 = await findShadowRoot(By.css(`#cells-template-login`)); 31 | 32 | let userInput = await shadowRoot1.findElement(By.css(`#user > input[type=text]`)); 33 | await userInput.sendKeys('username'); 34 | let passwordInput = await shadowRoot1.findElement(By.css(`#password > input[type=password]`)); 35 | await passwordInput.sendKeys('password'); 36 | await driver.takeScreenshot(); 37 | 38 | let button = await shadowRoot1.findElement(By.css(`bbva-button-default`)); 39 | button.click(); 40 | 41 | await driver.sleep(1000); 42 | await driver.takeScreenshot(); 43 | 44 | } 45 | 46 | async function findShadowRoot(selector) { 47 | /* 48 | Only works on open shadow roots. Cannot be used on closed shadow roots. 49 | Only CSS selectors work within the Shadow tree, do not use Xpath. 50 | */ 51 | let shadowHost = await driver.findElement(selector); 52 | let shadowRoot = await driver.executeScript(`return arguments[0].shadowRoot;`, shadowHost); 53 | return shadowRoot; 54 | } 55 | 56 | async function configureDriver() { 57 | await driver.manage().setTimeouts({ 58 | implicit: 7 * 1000, // If an element is not found, reattempt for this many milliseconds 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /examples/smtpServerAvailability.js: -------------------------------------------------------------------------------- 1 | /* 2 | A script that checks availability of an SMTP Server by connecting to the service and evaluating the response code. 3 | */ 4 | 5 | import { net } from 'thousandeyes'; 6 | import assert from 'assert'; 7 | runScript(); 8 | 9 | async function runScript() { 10 | // Set host and port 11 | let host = 'smtp.gmail.com'; 12 | let port = 587; 13 | await callServer(host, port); 14 | 15 | }; 16 | async function callServer(host, port) { 17 | const sock = await net.connect(port, host); 18 | sock.setEncoding('utf8'); 19 | let response = await sock.read(); 20 | 21 | // Send Hello message to the server 22 | await sock.writeAll('HELO ' + host + '\r\n'); 23 | response = await sock.read(); 24 | 25 | // Check entire response by uncommenting line below - must use IDE 26 | //console.log (response); 27 | 28 | // Change 250 to expected response code if different 29 | await checkCode(response,250); 30 | 31 | // Close connection 32 | await sock.writeAll('QUIT\r\n'); 33 | await sock.end() 34 | } 35 | 36 | async function checkCode(response,correctCode){ 37 | response = response.substring(0,3); 38 | if (response != correctCode) { 39 | assert.fail('Received wrong SMTP response code: ' + response); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/solveMathCaptcha.js: -------------------------------------------------------------------------------- 1 | import { By, Key } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | 8 | await configureDriver(); 9 | 10 | await driver.get('https://form.jotform.com/73302671092956'); 11 | // Click on 'START' 12 | await click(By.id(`jfCard-welcome-start`)); 13 | await click(By.id(`first_1`)); 14 | 15 | // Type in your name 16 | await typeText('John', By.id(`first_1`)); 17 | await typeText('Doe', By.id(`last_1`)); 18 | await click(By.css(`[data-type="control_fullname"] [aria-label="Next"]`)); 19 | 20 | // Type in your email 21 | await typeText('john.doe@thousandeyes.com', By.id(`input_2`)); 22 | await click(By.css(`[data-type="control_email"] [aria-label="Next"]`)); 23 | 24 | // Type in a test message 25 | await typeText('This is a test message', By.id(`input_3`)); 26 | await click(By.css(`[data-type="control_textarea"] [aria-label="Next"]`)); 27 | 28 | // Solve math based captcha 29 | await driver.switchTo().frame(driver.findElement(By.id(`customFieldFrame_4`))) 30 | let number = await driver.findElement(By.id(`number`)).getText(); 31 | let func = await driver.findElement(By.id(`function`)).getText(); 32 | let number2 = await driver.findElement(By.id(`number2`)).getText(); 33 | let result; 34 | if (func == '+') { 35 | result = parseInt(number) + parseInt(number2) 36 | } else if (func == '-') { 37 | result = parseInt(number) - parseInt(number2) 38 | } else if (func == '*') { 39 | result = parseInt(number) * parseInt(number2) 40 | } else { 41 | result = parseInt(number) / parseInt(number2) 42 | } 43 | await typeText(result, By.id(`result`)); 44 | await driver.switchTo().defaultContent() 45 | await click(By.css(`[data-type="control_widget"] [aria-label="Submit"]`)); 46 | 47 | } 48 | 49 | async function configureDriver() { 50 | await driver.manage().window().setRect({ 51 | width: 1200, 52 | height: 1373 53 | }); 54 | await driver.manage().setTimeouts({ 55 | implicit: 7 * 1000, // If an element is not found, reattempt for this many milliseconds 56 | }); 57 | } 58 | 59 | 60 | 61 | async function click(selector) { 62 | await simulateHumanDelay(); 63 | 64 | const configuredTimeouts = await driver.manage().getTimeouts(); 65 | const clickAttemptEndTime = Date.now() + configuredTimeouts.implicit; 66 | 67 | await reattemptUntil(attemptToClick, clickAttemptEndTime); 68 | 69 | async function attemptToClick() { 70 | await driver.findElement(selector) 71 | .click(); 72 | } 73 | } 74 | 75 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 76 | const TIME_BETWEEN_ATTEMPTS = 100; 77 | let numberOfAttempts = 0; 78 | let attemptError; 79 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 80 | try { 81 | numberOfAttempts += 1; 82 | await attemptActionFn(); 83 | } 84 | catch (error) { 85 | attemptError = error; 86 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 87 | continue; // Attempt failed, reattempt 88 | } 89 | attemptError = null; 90 | break; // Attempt succeeded, stop attempting 91 | } 92 | 93 | const wasAttemptSuccessful = !attemptError; 94 | if (!wasAttemptSuccessful) { 95 | throw attemptError; 96 | } 97 | } 98 | 99 | async function simulateHumanDelay() { 100 | await driver.sleep(550); 101 | } 102 | 103 | async function typeText(value, selector) { 104 | await simulateHumanDelay(); 105 | const element = await driver.findElement(selector); 106 | await element.clear(); 107 | await element.sendKeys(value); 108 | } 109 | -------------------------------------------------------------------------------- /examples/switchToNextTab.js: -------------------------------------------------------------------------------- 1 | import { By, Key } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | // Historically, the same browser has been securely re-used between multiple test 8 | // rounds. To prevent the browser from closing completely, the system has kept around 9 | // an extra tab, the ID of which we look up here. In the future, when browsers are not 10 | // reused across test rounds and there won't be a backup tab, this same code will continue 11 | // to behave as intended. 12 | const backupTabId = await getBackupTabId(); 13 | await driver.get('https://wikipedia.com'); 14 | 15 | await openInNewTab(By.css('a#js-link-box-en')); 16 | await switchToNextTab(backupTabId); 17 | 18 | await driver.sleep(1000); 19 | } 20 | 21 | async function openInNewTab(linkSelector) { 22 | await driver.findElement(linkSelector).sendKeys(Key.CONTROL, Key.RETURN); 23 | } 24 | 25 | async function getBackupTabId() { 26 | const currentTabId = await driver.getWindowHandle(); 27 | const allTabIds = await driver.getAllWindowHandles(); 28 | return allTabIds.filter(tabId => tabId !== currentTabId)[0]; 29 | } 30 | 31 | async function getAllWindowHandlesExcludingBackup(backupTabId) { 32 | const allTabIds = await driver.getAllWindowHandles(); 33 | return allTabIds.filter(tabId => tabId !== backupTabId); 34 | } 35 | 36 | async function switchToNextTab(backupTabId) { 37 | const currentTabId = await driver.getWindowHandle(); 38 | const allTabIds = await getAllWindowHandlesExcludingBackup(backupTabId); 39 | const currentTabIndex = allTabIds.findIndex(tabId => tabId === currentTabId); 40 | const nextTabId = allTabIds[currentTabIndex + 1] || allTabIds[0]; 41 | return driver.switchTo().window(nextTabId); 42 | } -------------------------------------------------------------------------------- /examples/switchToTabWithUrl.js: -------------------------------------------------------------------------------- 1 | /* 2 | Transaction scripts can open new pages in new tabs/windows. This example shows how to switch to a new tab/window based on the 3 | URL. 4 | Author: primoz@thousandeyes.com 5 | */ 6 | 7 | import { By, Key } from 'selenium-webdriver'; 8 | import { driver } from 'thousandeyes'; 9 | 10 | runScript(); 11 | 12 | async function runScript() { 13 | 14 | await driver.manage().setTimeouts({ 15 | implicit: 7 * 1000 // If an element is not found, reattempt for this many milliseconds 16 | }); 17 | 18 | await driver.get('https://developer.cisco.com/docs/thousandeyes/'); 19 | 20 | await driver.findElement(By.xpath(`//a[text()='API Changelog']`)).click(); 21 | await openInNewTab(By.xpath(`//a[text()='Changelog']`)); 22 | 23 | await switchToTabWithUrl('https://docs.thousandeyes.com'); 24 | await driver.takeScreenshot(); 25 | 26 | await switchToTabWithUrl('https://developer.cisco.com/'); 27 | await driver.takeScreenshot(); 28 | } 29 | 30 | async function openInNewTab(linkSelector) { 31 | await driver.findElement(linkSelector).sendKeys(Key.CONTROL, Key.RETURN); 32 | } 33 | 34 | async function switchToTabWithUrl(url) { 35 | const configuredTimeouts = await driver.manage().getTimeouts(); 36 | const attemptEndTime = Date.now() + configuredTimeouts.implicit; 37 | while (Date.now() < attemptEndTime) { 38 | for (const tab of await driver.getAllWindowHandles()) { 39 | await driver.switchTo().window(tab); 40 | const currentTabUrl = await driver.getCurrentUrl(); 41 | if (currentTabUrl.includes(url)) { 42 | return; 43 | } 44 | } 45 | await driver.sleep(200); 46 | } 47 | throw new Error('Could not find a tab with url: ' + url); 48 | } 49 | -------------------------------------------------------------------------------- /examples/takeScreenshot.js: -------------------------------------------------------------------------------- 1 | import { driver } from 'thousandeyes'; 2 | 3 | runScript(); 4 | 5 | async function runScript() { 6 | await driver.get('https://google.com'); 7 | 8 | // Takes a screenshot of the whole page at the time this function is called. Initially you're limited to 3 screenshots per test. 9 | // These screenshots are viewable in either the ThousandEyes recorder or the views of the ThousandEyes app. 10 | // Calling this function will take the screenshot right away, so make sure to wait for the visibility of any elements 11 | // you want to capture before calling, otherwise the screenshot may just show a white/partially loaded page. 12 | await driver.takeScreenshot(); 13 | } 14 | -------------------------------------------------------------------------------- /examples/timePortionOfScript.js: -------------------------------------------------------------------------------- 1 | import { driver, markers } from 'thousandeyes'; 2 | 3 | runScript(); 4 | 5 | async function runScript() { 6 | // Creates a new marker with the name of "PageLoad" 7 | // This will track the time between the start and stop call 8 | // and will show up in the views and API with this name. 9 | markers.start('PageLoad'); 10 | await driver.get('https://google.com'); 11 | markers.stop('PageLoad'); 12 | } 13 | -------------------------------------------------------------------------------- /examples/usingCredentials.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver, credentials } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | // Calling `credentials.get` will allow you to retrieve sensitive tokens/passwords saved 8 | // in the ThousandEyes credential store. 9 | // When working in the Recorder, make sure you've either imported or added the credential info by hand 10 | // When working in the thousandeyes app, make sure the credential is associated with the test in question 11 | const password = credentials.get('MyPasswordInCredentialStore'); 12 | await driver.findElement(By.css('.password')) 13 | .sendKeys(password); 14 | } 15 | -------------------------------------------------------------------------------- /examples/usingTOTPTwoFactorAuth.js: -------------------------------------------------------------------------------- 1 | import { By } from 'selenium-webdriver'; 2 | import { driver, credentials, authentication } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | // Calling `credentials.get` retrieves sensitive tokens/passwords saved 8 | // in the ThousandEyes credential store. 9 | // When working in the Recorder, make sure you've either imported or added the credential info by hand. 10 | // When working in the ThousandEyes app, make sure the credential is associated with the test in question. 11 | const secretToken = credentials.get('TOTP Secret Token'); 12 | 13 | // Calling `authentication.getTimeBasedOneTimePassword` generates a 6-digit 14 | // temporary authentication code to be used in conjunction with a password on sites that 15 | // support 2 factor authentication with time-based one-time passwords (TOTPs). 16 | var totp = authentication.getTimeBasedOneTimePassword(secretToken); 17 | 18 | await driver.findElement(By.css('.totp_auth_code')) 19 | .sendKeys(totp); 20 | } 21 | -------------------------------------------------------------------------------- /examples/waitForCondition.js: -------------------------------------------------------------------------------- 1 | import { until } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | // An explicit wait until the page title is "Some Title", which will time out after 1000 milliseconds 8 | // Selenium webdriver provides many builtin conditions to wait for, such as element visibility or interactibility 9 | // To avoid longer transaction times, it's often good practice to avoid mixing implicit and explicit waits. 10 | await driver.wait(until.titleIs('Some Title'), 1000); 11 | }; 12 | -------------------------------------------------------------------------------- /examples/waitForDownload.js: -------------------------------------------------------------------------------- 1 | import { downloads } from 'thousandeyes'; 2 | 3 | runScript(); 4 | 5 | async function runScript() { 6 | // This will wait up to 60 seconds for MyFilename.pdf to finish downloading. 7 | // If it's already been downladed, it will finish immediately. 8 | // If it's not currently downloading, it will wait for the download to start 9 | await downloads.waitForDownload('MyFilename.pdf', 60 * 1000); 10 | }; 11 | -------------------------------------------------------------------------------- /examples/waitForUrl.js: -------------------------------------------------------------------------------- 1 | import { By, Key } from 'selenium-webdriver'; 2 | import { driver } from 'thousandeyes'; 3 | 4 | runScript(); 5 | 6 | async function runScript() { 7 | 8 | await driver.manage().setTimeouts({ 9 | implicit: 7 * 1000, // If an element is not found, reattempt for this many milliseconds 10 | }); 11 | 12 | await driver.get('https://google.com'); 13 | 14 | await typeText('thousandeyes', By.name(`q`)); 15 | 16 | // Click on 'Google Search' 17 | await click(By.xpath(`//input[@type='submit']`)); 18 | 19 | // Click on the first link in search results 20 | await click(By.xpath(`//h3[1]`)); 21 | 22 | // Wait for ThousandEyes URL 23 | await waitForUrl('thousandeyes.com'); 24 | 25 | } 26 | 27 | async function typeText(value, selector) { 28 | const element = await driver.findElement(selector); 29 | await element.clear(); 30 | await element.sendKeys(value); 31 | } 32 | 33 | async function click(selector) { 34 | const configuredTimeouts = await driver.manage().getTimeouts(); 35 | const clickAttemptEndTime = Date.now() + configuredTimeouts.implicit; 36 | 37 | await reattemptUntil(attemptToClick, clickAttemptEndTime); 38 | 39 | async function attemptToClick() { 40 | await driver.findElement(selector) 41 | .click(); 42 | } 43 | } 44 | 45 | async function reattemptUntil(attemptActionFn, attemptEndTime) { 46 | const TIME_BETWEEN_ATTEMPTS = 100; 47 | let numberOfAttempts = 0; 48 | let attemptError; 49 | while (Date.now() < attemptEndTime || numberOfAttempts === 0) { 50 | try { 51 | numberOfAttempts += 1; 52 | await attemptActionFn(); 53 | } 54 | catch (error) { 55 | attemptError = error; 56 | await driver.sleep(TIME_BETWEEN_ATTEMPTS); 57 | continue; // Attempt failed, reattempt 58 | } 59 | attemptError = null; 60 | break; // Attempt succeeded, stop attempting 61 | } 62 | 63 | const wasAttemptSuccessful = !attemptError; 64 | if (!wasAttemptSuccessful) { 65 | throw attemptError; 66 | } 67 | } 68 | 69 | async function waitForUrl(url) { 70 | const configuredTimeouts = await driver.manage().getTimeouts(); 71 | const attemptEndTime = Date.now() + configuredTimeouts.implicit; 72 | let currentUrl = await driver.getCurrentUrl(); 73 | while (Date.now() < attemptEndTime) { 74 | currentUrl = await driver.getCurrentUrl(); 75 | if (currentUrl.includes(url)) { 76 | return; 77 | } 78 | await driver.sleep(200); 79 | } 80 | throw new Error('Wait for URL ' + url + ' timed out. Current URL: ' + currentUrl); 81 | } 82 | --------------------------------------------------------------------------------