使用瀏覽器 Script 進行瀏覽器測試

您可以使用 Node.js 型 Script 及 Selenium型 API 來啟用 Instana 瀏覽器測試。 使用預先定義的廣域變數及 Instana 瀏覽器測試 API ,您可以建立及執行瀏覽器 Script 測試,以模擬 Web 瀏覽器中的使用者互動。

Instana 瀏覽器 Script 參照

如果要建立瀏覽器 Script 測試來啟用瀏覽器測試,您需要廣域變數和模組。

廣域變數

表格中所概述的預先定義廣域變數可加速 Script 開發。

名稱 類型 詳細資料
$driver @types/selenium-webdriver Selenium WebDriverJS 的類型定義。
$browser ThenableWebDriver 啟用 WebDriver 用戶端的封套,它提供對瀏覽器的控制,並容許直接在 Promise<WebDriver> 上呼叫 WebDriver 方法,而不呼叫 then
$secure Dict<string> 用來存取使用者認證的變數。
$util UserUtil 公用程式函數,例如 $util.secrets
$network Network 用來設定 Proxy 的網路公用程式。
$synthetic Dict<string> 環境變數。
$attributes UserInsight 用來新增及操作自訂屬性的變數。

預先定義廣域變數的用法和宣告如下:

$synthetic

您可以使用 $synthetic.var_name 來存取 Script 中的環境和執行時期變數。 部分預先定義的變數如下:

  • $synthetic.LOCATION$synthetic.pop: 用來存取位置標籤,這是 controller.location 變數中的第一部分。
  • $synthetic.TEST_ID$synthetic.id: 用來存取所執行虛構測試的 ID。
  • $synthetic.TEST_NAME$synthetic.testName: 用來存取所執行虛構測試的測試標籤。
  • $synthetic.TIME_ZONE$synthetic.timeZone: 用來存取執行虛構測試之 PoP 的時區。
  • $synthetic.JOB_ID$synthetic.taskId: 用來存取播放工作或作業的 ID ,它也是結果 ID。
  • $synthetic.testType: 用來存取測試類型。
  • $synthetic.description: 用來存取測試說明。
  • $synthetic.labels.xxx: 用來存取測試定義的 customProperties 區段中所定義的自訂內容。

您也可以在 Helm Chart 的 values.yaml 檔中定義自訂環境變數。 請參閱下列範例:

controller:
  customProperties: "tag1=value1;tag2=value2"

若要存取這些自訂變數,請在 Script 中使用 $synthetic.tag1$synthetic.tag2

如果要存取測試定義的 customProperties 區段中所定義的自訂內容,以及 Script 中的內容值,請使用 $synthetic.labels.xxx。 請參閱下列範例:

// to print test custom tags/labels
console.log('Test Label $synthetic.labels.Team: ' + $synthetic.labels.Team);
console.log('Test Label $synthetic.labels.Purpose: ' + $synthetic.labels.Purpose);

$secure

Instana 瀏覽器 Script 支援使用者認證安全地儲存密鑰,例如登入使用者名稱和密碼。 您可以使用 $secure.MY_SECURE_CREDENTIAL 來參照 Script 中預先定義的安全認證。

若要使用 Synthetic Open API 建立認證,請完成下列步驟:

  1. 請確定您具有適當的 許可權設定
  2. 透過傳遞 credentialNamecredentialValue 變數,使用 Synthetic OpenAPI 來建立認證。

然後,在瀏覽器 Script 中,使用 $secure.credentialName 來參照所建立的認證,例如 $secure.password$secure.username

$attributes

您可以使用 $attributes 變數來新增或取得監視資料的自訂屬性。 這些自訂屬性也會與其他預設監視結果屬性一起儲存在監視結果中。

Instana Synthetic 監視支援下列 API 來配置自訂屬性:

  • $attributes.set(key, value): 設定索引鍵或值。
  • $attributes.get(key): 傳回所提供金鑰的值。
  • $attributes.getKeys(): 傳回所有索引鍵的陣列。
  • $attributes.has(key): 如果金鑰存在,則傳回 true
  • $attributes.unset(key): 移除指定的索引鍵或值。
  • $attributes.unsetAll(): 移除所有自訂資料。

下列範例顯示 $attributes API:

$attributes.set(xxx, yyy) // sets tag xxx to value yyy
let v = $attributes.get(xxx) // sets variable v to the value of tag xxx

$network

在 Instana 虛構監視中支援下列 API ,以配置 Proxy 伺服器:

  • $network.setProxy(string proxy): 設定要用於所有要求的 Proxy 伺服器 (HTTP、HTTP)。
  • $network.setProxyForHttp(string proxy): 設定 Proxy 伺服器只用於 HTTP 要求。
  • $network.setProxyForHttps(string proxy): 設定 Proxy 伺服器只用於 HTTP 要求。
  • $network.setProxyPAC(string pac, object auth): 透過 Proxy 自動配置 (PAC) Script 來設定 Proxy 伺服器。
  • $network.setProxyAdvanced(object proxy): 使用 Chrome/Firefox 延伸 API 支援的格式進行 Proxy 處理來設定 Proxy 配置。
  • $network.clearProxy(): 移除 Proxy 配置。
  • $network.getProxy(): 傳回 Proxy 配置。

$util.secrets

您可以使用 $util.secrets 安全 API 來編寫機密資料。

附註: 在登入記載檔案並將檔案傳送至後端之前,請使用星號 (*) 字元遮罩所有已知的機密性資訊。

若要從 URL 編寫密鑰,請在 Script 中使用 $util.secrets.setURLSecretsRegExps 指令。 請參閱下列範例:

// to redact 'key', 'password' and 'secret' query parameters in URL
$util.secrets.setURLSecretsRegExps([/key/i, /password/i, /secret/i]);

呼叫此 API 之後,會收集 URL https://example.com/accounts/status?key=mykey123&secret=mysecret ,並在 Instana 使用者介面中顯示為 https://example.com/accounts/status?key=*&secret=*

Instana Synthetic 會移除所有要求及回應標頭,以保持與 Instana 現行設計一致。

依預設, Instana 在追蹤 HTTP 呼叫時不會收集 HTTP 標頭,以避免傳送機密資料。

瀏覽器測試 API 參照

瀏覽器測試支援 Selenium型 API ,以及超過 30 個其他延伸瀏覽器測試 API。 如需相關資訊,請參閱 API 參考資料

撰寫瀏覽器 Script 測試

您可以針對下列動作撰寫瀏覽器 Script 測試,以啟用瀏覽器測試。 範例程式碼說明如何使用常用 API 來撰寫瀏覽器 Script 測試。 如需完整範例程式碼的相關資訊,請參閱 Script 範例 一節。

實作基本動作

下列範例程式碼顯示如何撰寫瀏覽器 Script 測試,以實作下列基本動作:

  • 準備標頭: 若要在存取網站之前自訂要求標頭,請使用下列指令:

    await $browser.addHeader("test", "test");
    await $browser.addHeaders({ "test1": "test1", "test2": "test2" });
    await $browser.deleteHeaders(["test1", "test2"]);
    $browser.getHeaders().forEach((value, key) => console.log(">>>>>>>>>>>>>>>>>>>", "User customized headers: ", key + "=" + value));
    
  • 控制存取權: 若要使用拒絕及容許清單來控制存取權,請使用下列指令:

    await $browser.addHostnamesToDenylist(["*google*", "*timeanddate*"]);
    await $browser.deleteHostnameFromDenylist("*timeanddate*");
    await $browser.get("https://www.google.com");
    

    在執行這些指令之後搜尋 google 時,您會看到訊息「wwww.google.com 已封鎖」。

  • 開啟 URL: 若要在瀏覽器中開啟 URL 以存取網站,請使用下列指令:

    await $browser.get("http://www.bing.com");
    
  • 設定休眠時間: 若要在瀏覽器中設定休眠選項,請使用下列指令。 此參數是休眠的時間量 (毫秒)。 在下列範例中,瀏覽器休眠 2 秒。

    await $browser.sleep(2000);
    
  • 將主張與頁面標題搭配使用: 主張在您的測試中是很重要的,以判斷測試結果。 若要使用主張與頁面標題或頁面來源搭配,請使用下列程式碼。 您可以使用程式庫 const assert = require("assert").strict; 或 Chai 主張程式庫 const { assert } = require("chai");。 使用 console.log 來登入主控台輸出及主控台日誌檔。

    const { assert } = require("chai");
    
    await $browser.get("http://www.timeanddate.com");
    let page = await $browser.getPageSource();
    assert.isTrue(page.includes("<title>timeanddate.com</title>"));
    console.log(">>>>>>>>>>>>>>>>>>>", "Page title: ", await $browser.getTitle());
    
  • 取得階段作業: 若要取得階段作業的相關資訊,請使用下列指令:

    let session = await $browser.getSession();
    console.log(">>>>>>>>>>>>>>>>>>>", "Session id: ", session.getId());
    
  • 最大化視窗大小: 若要最大化瀏覽器視窗大小,請使用下列指令:

    $browser.manage().window().maximize();
    
  • 取得擷取畫面: 若要取得瀏覽器的擷取畫面,請使用下列指令:

    await $browser.takeScreenshot();
    

尋找頁面元素並與之互動

  • 尋找元素並執行動作: 若要尋找元素,請使用 $$$。 下列範例顯示如何依類別 selector 來尋找元素,並使用 ActionSequence來傳送索引鍵:

    <input id="sb_form_q" class="sb_form_q" name="q" type="search">
    
    await $browser.get("http://www.bing.com");
    console.log(">>>>>>>>>>>>>>>>>>>", "Actions of search");
    await $browser.$(".sb_form_q");
    $browser.actions().sendKeys("synthetic").sendKeys($driver.Key.ENTER).perform();
    
  • 在頁面上等待並尋找元素: 您可以使用下表中的指令進行特定搜尋:

    名稱 使用
    $browser.waitForAndFindElement 等待並尋找元素,直到元素可見或達到逾時值為止。
    $browser.findElements($driver.By.css('select')) 在具有定位器的頁面上搜尋多個元素。
    $browser.$('#boxyear') 依 ID 尋找元素。
    $browser.$$(".rd-box") 依類別尋找元素。
    elem.$('h2').$('a').getText() 依元素名稱尋找元素。

    下列範例程式碼顯示如何在頁面上等待及尋找元素:

    let elem = await $browser.waitForAndFindElement($driver.By.id("boxyear"), 1000);
    assert.equal("2022", await elem.getAttribute("value"));
    assert.equal("2022", await $browser.$('#boxyear').getAttribute("value"));
    
    let selects = await $browser.findElements($driver.By.css('select'));
    assert.equal(2, selects.length);
    
    let elems = await $browser.$$(".rd-box");
    console.log(await elems[0].$('h2').$('a').getText());
    

套用條件

  • 等到 標題變成條件: 若要套用等到標題變成條件,請使用下列指令:

    await $browser.wait($driver.until.titleContains('Year 2022 Calendar'), 10000);
    
  • 等待及輪詢直到逾時條件: 若要套用等待及輪詢直到逾時條件,請使用下列指令:

    附註: 下列範例是與舊版相容的承諾鏈呼叫。 請勿在現代 JavaScript中使用程式碼。 請改為使用具有 Async 和 Await 的程式碼現代非同步 JavaScript 。

    $browser.get("https://www.google.com");
    $browser.wait(function () {
      return $browser.getTitle().then(function (title) {return title.includes("Google");});
    }, 10000).then(function () {
      return $browser.findElement($driver.By.linkText("About")).click();
    }).then(function () {
      return $browser.navigate().back();
    });
    
  • 往回導覽: 若要在瀏覽器中移回先前存取的頁面,請使用下列指令:

    await $browser.navigate().back();
    
  • 切換至新標籤: 若要取得現行視窗控點並切換至新視窗,以在新標籤中開啟新頁面並切回原始視窗,請使用下列程式碼:

    console.log("Windows handle: ", await $browser.getWindowHandle(), "Current url: ", await $browser.getCurrentUrl());
    let originalWindow = await $browser.getWindowHandle();
    await $browser.switchTo().newWindow();
    await $browser.get("http://www.timeanddate.com");
    await $browser.switchTo().window(originalWindow);
    await $browser.takeScreenshot();
    

移動滑鼠並傳送按鍵

  • 按一下滑鼠: 若要移動滑鼠,請使用下列指令:

    const element = await $browser.waitForAndFindElement(by, timeout);
    const out = $browser.actions().move({origin: element}).press().release().perform();
    
  • 滑鼠浮動: 如果要將滑鼠浮動在瀏覽器元素上,請使用下列指令:

    const element = await $browser.waitForAndFindElement(by, timeout);
    const out = $browser.actions().move({origin: element, duration: 2000}).perform();
    
  • 傳送金鑰: 若要傳送金鑰,請使用下列指令:

    await $browser.actions().sendKeys("synthetic").perform();
    

處理陰影 DOM 元素

若要存取 Shadow DOM 元素,您可以使用 getShadowRoot() 函數。 虛構 PoP Helm Chart 1.1.1 或更新版本支援新方法。 您必須將虛構 PoP 版本 升級 至 1.1.1 或更新版本,以啟用對 Shadow DOM 互動的支援。

let shadowHost = await $browser.waitForAndFindElement(shadowHostSelector, timeout);
let shadowRoot = await shadowHost.getShadowRoot();
let element = await shadowRoot.findElement(elementSelector);
console.log("element text is:", await element.getText(), ", id is:", await element.getId());

設定 Proxy 以存取網站

  • 如果要使用 $network$browser 變數來設定 HTTP 和 HTTP 要求的 Proxy ,請使用下列指令。 您可以使用 stringurl.URL來設定 Proxy。

    await $network.clearProxy();
    await $network.setProxyForHttp(proxyServer);
    website = await accessWebSite();
    checkHostname("www.google.com.hk", website)
    
    await $network.clearProxy();
    let testURL = new URL("http://" + sslProxyServer);
    await $network.setProxyForHttps(testURL);
    website = await accessWebSite();
    checkHostname("www.google.com", website);
    
  • 若要為具有要過濾的略過清單的所有要求設定 Proxy ,請使用下列指令。 在下列範例中執行此指令之後, www.google.com沒有可用的 Proxy。

    await $network.clearProxy();
    await $network.setProxy(proxyServer, "www.google.com");
    console.log(">>>>>>>>>>>>>>>>>>>", "Bypass list: ", $network.getProxy().rules.bypassList);
    website = await accessWebSite();
    checkHostname("www.google.com.hk", website);
    

編寫密鑰

若要編寫 URL 中的機密資料,請使用下列指令。 在下列範例中, URL 編寫為 "url": "https://www.bing.com/?mkt=zh-CN&toHttps=1&redig=*"

$util.secrets.setURLSecretsRegExps([/redig/i]);
console.log(">>>>>>>>>>>>>>>>>>>", "Access bing");
await $browser.get("http://cn.bing.com");
await $browser.wait($driver.until.titleContains('Bing'), 10000);

管理模組

您可以在瀏覽器測試 Script 中匯入協力廠商、核心或選用模組。

匯入選用模組

下列範例顯示匯入主張模組 chai的指令:

var assert = require('chai').assert;

支援的核心模組

Instana 瀏覽器測試支援 Node.js 16.19.1 或更新版本中的 Node.js 核心模組。

附註: 為了提供更安全的執行時期, Instana 瀏覽器測試不支援數個內建模組或 API ,包括 processchild_processfs 寫入或 rm 作業。 如果您在測試 Script 或組合 Script 中使用這些 API ,則測試結果可能會失敗,並出現下列錯誤訊息。 因此,請勿在測試 Script 中使用這些 API。

[SyntheticPoP] [ERROR] process.exit is not allowed. This API is blocked by playback engine.

支援的協力廠商模組

Instana Synthetic 監視支援下列協力廠商模組:

  • @aws-sdk/client-s3 3.350.0
  • atob 2.1.2
  • basic-ftp 4.6.6
  • btoa 1.2.1
  • chai 4.3.10
  • 顏色 1.4.0
  • consoleplusplus 1.4.4
  • crypto-js 4.2.0
  • faker 5.5.3
  • 取得 11.8.6
  • joi 17.7.1
  • js-yaml 4.1.0
  • ldapauth-fork 5.0.2
  • lodash 4.17.21
  • 時刻 2.29.4
  • net-snmp 3.6.3
  • protocol-緩衝區 4.2.0
  • q 1.5.1
  • re2 1.17.4
  • ssh2-sftp-client 7.2.3
  • ssl-checker 2.0.7
  • 文字編碼 0.7.0
  • Thrift 0.14.2
  • totp-generator 0.0.14
  • 強硬 Cookie 4.1.3
  • 底線 1.13.3
  • url-parse 1.5.10
  • urllib 2.41.0
  • uuid 3.4.0
  • 驗證器 13.7.0
  • ws 7.5.5
  • xml2js 0.5.0

Script 範例

下列範例包括大部分瀏覽器測試 API 及完整範例程式碼。

附註: 這些範例僅供示範之用。 您可以從簡式測試 Script 開始。

API 的完整範例程式碼
const { assert } = require("chai");

(async function () {
  /**
   * manipulate headers and retrieve all the customized headers
   */
  await $browser.addHeader("test", "test");
  await $browser.addHeaders({ "test1": "test1", "test2": "test2" });
  await $browser.deleteHeaders(["test1", "test2"]);
  $browser.getHeaders().forEach((value, key) => console.log(">>>>>>>>>>>>>>>>>>>", "User customized headers: ", key + "=" + value));

  /**
   * manipulate deny and allow list
   */
  await $browser.addHostnamesToDenylist(["*google*", "*timeanddate*"]);
  await $browser.deleteHostnameFromDenylist("*timeanddate*");

  /**
   * open url www.bing.com
   * find element by class selector
   * send keys to search Synthetic in the page
   */
  console.log(">>>>>>>>>>>>>>>>>>>", "Access bing page");
  await $browser.get("http://www.bing.com");
  console.log(">>>>>>>>>>>>>>>>>>>", "Actions of search");
  await $browser.$(".sb_form_q");
  await $browser.actions().sendKeys("synthetic").sendKeys($driver.Key.ENTER).perform();

  /**
   * samples of getting current window handle
   * switch to a new window
   * open new page in new tab
   * 
   */
  console.log(">>>>>>>>>>>>>>>>>>>", "Windows handle: ", await $browser.getWindowHandle(), "Current url: ", await $browser.getCurrentUrl());
  console.log(">>>>>>>>>>>>>>>>>>>", "switch to calendar tab");
  let originalWindow = await $browser.getWindowHandle();
  await $browser.switchTo().newWindow();

  /**
   * navigate to www.timeanddate.com
   * assert page title by getTitle() api
   * assert page title by getPageSource() api
   */
  console.log(">>>>>>>>>>>>>>>>>>>", "Access time and date page");
  await $browser.get("http://www.timeanddate.com");
  let page = await $browser.getPageSource();
  assert.isTrue(page.includes("<title>timeanddate.com</title>"));
  console.log(">>>>>>>>>>>>>>>>>>>", "Page title: ", await $browser.getTitle());

  /**
   * get session and session id
   */
  let session = await $browser.getSession();
  console.log(">>>>>>>>>>>>>>>>>>>", "Session id: ", session.getId());

  /**
   * manage window size
   */
  await $browser.manage().window().maximize();

  /**
   * samples of finding element or elements by id or tag name
   */
  let elem = await $browser.waitForAndFindElement($driver.By.id("boxyear"), 1000);
  await elem.click();
  assert.equal("2023", await elem.getAttribute("value"));
  assert.equal("2023", await $browser.$('#boxyear').getAttribute("value"));

  let selLength = (await $browser.$$("<select>")).length;
  console.log(">>>>>>>>>>>>>>>>>>>", `There are ${selLength} selects`);
  let selects = await $browser.findElements($driver.By.css('select'));
  assert.equal(2, selects.length);
  for (const select of selects) {
    console.log(">>>>>>>>>>>>>>>>>>>", "Found select element: ", await select.getAttribute("name"));
  }

  let elems = await $browser.$$(".rd-box");
  for (const elem of elems) {
    console.log(">>>>>>>>>>>>>>>>>>>", "Four box titles: ", await elem.$('h2').$('a').getText());
  }

  /**
   * samples of finding a input button by value and click the button
   * wait until the title comes out
   * take a screenshot
   */
  await $browser.$('[value="View calendar"]').click();
  await $browser.wait($driver.until.titleContains('Year 2023 Calendar'), 10000);
  await $browser.takeScreenshot();

  /**
   * switch back to original window
   * take screenshot
   */
  await $browser.switchTo().window(originalWindow);
  await $browser.takeScreenshot();

  /**
   * samples of promise chain invocation, which is not recommended but compatible
   * samples of waiting and polling function until timeout
   */
  $browser.deleteHostnameFromDenylist("*google*");
  $browser.get("https://www.google.com");
  // Call the wait function.
  $browser.wait(function () {
    return $browser.getTitle().then(function (title) {
      return title.includes("Google");
    });
    //If the condition isn't satisfied within 10000 milliseconds (10 seconds), proceed anyway.
  }, 10000).then(function () {
    return $browser.findElement($driver.By.linkText("About")).click();
  }).then(function () {
    return $browser.navigate().back();
  });

})();

Proxy 的完整範例程式碼

附註: 請勿執行此範例程式碼。 您必須變更 Proxy 伺服器和主張。

const url = require("url");
const URL = url.URL;
const { assert } = require("chai");

let website;
let proxyServer = "proxyHost:proxyPort";
let sslProxyServer = "username:password@proxyHost:proxyPort";

async function accessWebSite(url) {
  console.log(">>>>>>>>>>>>>>>>>>>", "Proxy configuration is: ", JSON.stringify($network.getProxy()));
  console.log(">>>>>>>>>>>>>>>>>>>", `Access website ${url}`);
  await $browser.get(url);
  await $browser.findElement($driver.By.name('q')).click();
  await $browser.actions().sendKeys('synthetic').sendKeys($driver.Key.ENTER).perform();
  await $browser.takeScreenshot();
  return $browser.getCurrentUrl();
}

function checkHostname(hostname, website) {
  let actualURL = new URL(website);
  console.log(">>>>>>>>>>>>>>>>>>>", "Hostname is: ", actualURL.hostname);
  assert.equal(actualURL.hostname, hostname);
}

(async function () {
  /**
   * samples of proxy configuration settings with await asynchronous
   * using $network tools
   */
  console.log(">>>>>>>>>>>>>>>>>>>", "Clear proxy configuration");
  await $network.clearProxy();
  console.log(">>>>>>>>>>>>>>>>>>>", "Set proxy configuration for HTTPS request with string");
  await $network.setProxyForHttps(proxyServer);
  website = await accessWebSite("https://www.bing.com");
  checkHostname("www.bing.com", website)

  console.log(">>>>>>>>>>>>>>>>>>>", "Clear proxy configuration");
  await $network.clearProxy();
  console.log(">>>>>>>>>>>>>>>>>>>", "Set proxy configuration for HTTPS request with URL object");
  let testURL = new URL("http://" + sslProxyServer);
  await $network.setProxyForHttps(testURL);
  website = await accessWebSite("https://www.google.com");
  checkHostname("www.google.com", website);


  /**
   * samples of proxy configuration settings with bypass list
   */
  await $network.clearProxy();
  await $network.setProxy(proxyServer, "www.google.com");
  console.log(">>>>>>>>>>>>>>>>>>>", "Proxy config: ", $network.getProxy());
  website = await accessWebSite("https://www.bing.com");
  checkHostname("www.bing.com", website);
})();
完成動作的範例程式碼
let click = async (message, by, timeout = 60000) => {
    await console.log(`Click on ${message} >> ${by} << `);
    try {
        const element = await $browser.waitForAndFindElement(by, timeout);
        const out = $browser.actions().move({origin: element}).press().release().perform();
        return out;
    } catch (err) {
        console.error(`\ncatch(click): ${err.message}`);
        await $browser.takeScreenshot();
        throw err;
    }
}

let hover = async (message, by, timeout = 60000) => {
    await console.log(`Hover on ${message} >> ${by} << `);
    try {
        const element = await $browser.waitForAndFindElement(by, timeout);
        const out = $browser.actions().move({origin: element, duration: 2000}).perform();
        return out;
    } catch (err) {
        console.error(`\ncatch(hover): ${err.message}`);
        await $browser.takeScreenshot();
        throw err;
    }
}

(
    async function () {
        console.log(">>>>>>>>>>>>>>>>>>>", "Access bing page");
        await $browser.get("http://www.bing.com");
        console.log(">>>>>>>>>>>>>>>>>>>", "Actions of search");
        await hover("search image", $driver.By.xpath("//img[@id='sbi_b']"));
        await $browser.sleep(3000);
        await $browser.actions().sendKeys("synthetic").perform();
        await click("search button", $driver.By.xpath("//label[@id='search_icon']"));
    }
)();
自訂屬性的完整範例程式碼

附註: 請使用 $attributes 來自訂屬性。

(async function () {
  /**
   * samples of customization of Synthetic monitoring attributes
   * using $attributes
   */
  console.log(">>>>>>>>>>>>>>>>>>>", "Access office365 page");
  await $browser.get("https://www.office365.com");

  // find the element and click the elemment
  const element = await $browser.waitForAndFindElement(
    $driver.By.xpath("//li[@class='w-md-col-4 mb-2 accordion-list-show']//h3//button"),
    10000
  );

  // scroll down to the element
  console.log(">>>>>>>>>>>>>>>>>>>", "scroll down to the element");
  await $browser.executeScript("arguments[0].scrollIntoView()", element);
  const text = await element.getText();
  const tag = await element.getTagName();
  console.log(`element tag: ${tag}, text: ${text}`);
  $attributes.set(`Element_Tag`, tag);
  $attributes.set(`Element_Text`, text);

  for(let key of $attributes.getKeys()) {
    console.log(">>>>>>>>>>>>>>>>>>>", key, " = ", $attributes.get(key));
  }
})();