【Salesforce自動操作 第2回】プルダウン選択の自動化【vba, chrome拡張機能】

目次

前回のおさらい

Chrome拡張機能からケース作成画面のDOMを取得することができるようになりました。

今回やること

プルダウン項目の値を設定していきます。優先度を初期値「Medium」から「High」に切り替えます。

ソースコード 全体

/**
 * プルダウンを選択する
 * @param {string} target_label_name 画面上の表示ラベル名
 * @param {string} option_value 設定値
 */
async function selectPulldown(target_label_name, option_value) {
    const container = document.querySelector('.actionBody');
    const items = container.querySelectorAll('button[role="combobox"]');

    // 該当プルダウンを検索する。
    for (let i = 0; i < items.length; i++) {
        const item = items[i];
        const aria_label = item.getAttribute('aria-label');
        if (!aria_label) {
            continue;
        }
        // ラベル名を取得する。
        const label_name = aria_label.split(',')[0];
    
        // 該当プルダウンをクリック
        if (label_name == target_label_name) {
            
            // 0.01秒待機
            item.click();
            await sleep(10);

            // 値の一致するオプションをクリックする。
            const options = item.parentNode.parentNode.parentNode.querySelectorAll('lightning-base-combobox-item');
            for (let j = 0; j < options.length; j++) { 
           const option = options[j]; 
                const val = option.getAttribute('data-value'); 
                if (val == option_value) { 
                    option.click(); 
                }
            } 
        } 
    } 
} 
/** * 指定時間待機する 
 * @param {integer} ms 
 */ 
async function sleep(ms) { await new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}

async function main() {
    await selectPulldown('優先度', 'High');
}
main();

解説

ノード階層

まず、Salesforceのhtmlは必要な箇所のみ抜粋すると以下のような階層をしています。

<!-- ルート -->
<div class="actionBody">
    <!-- オブジェクト -->
    <records-record-layout-item field-label="優先度">
        <lightning-combobox>
            <!-- ラベル -->
            <label class="slds-form-element__label">
                優先度
            </label>
            <!-- プルダウン -->
            <div lightning-combobox_combobox>
                <div lightning-basecombobox_basecombobox class="slds-dropdown-trigger_click">
                    <div lightning-basecombobox_basecombobox>
                        <button role="combobox" data-value="Medium" aria-label="優先度, Medium">
                        </button>
                    </div>
                </div>
            </div>
        </lightning-combobox>
    </records-record-layout-item>
</div>
  • actionBodyは全入力項目を包括するページ全体に位置するタグです。
  • records-record-layout-itemはテキストやプルダウンなどのオブジェクトごとのルートタグです。

値を変更したいプルダウンを特定するためにはラベル名を取得する必要があります。

ラベル名はbuttonタグのaria-label に設定されているので、まずはそれを取得します。

ラベルの取得

    const container = document.querySelector('.actionBody');
    const items = container.querySelectorAll('button[role="combobox"]'); // (1)

    // 該当プルダウンを検索する。
    for (let i = 0; i < items.length; i++) {
        const item = items[i];
        const aria_label = item.getAttribute('aria-label');
        if (!aria_label) { // (2)
            continue;
        }
        // ラベル名を取得する。
        const label_name = aria_label.split(',')[0]; // (3)
  1. button[role="combobox"] で全てのプルダウンを列挙しています。
  2. 入力項目でないボタンの場合、aria_labelが設定されていません。
    そのままラベルを取得しようとするとエラーになってしまうので、属性有無を判定しています。
  3. 文字列「優先度, Medium」からラベル名「優先度」のみを取り出します。

選択肢の表示

        // 該当プルダウンをクリック
        if (label_name == target_label_name) {
      
            // 0.01秒待機
            item.click(); // (1)
            await sleep(10); // (2)
            // (中略)
        }
/** 
 * 指定時間待機する 
 * @param {integer} ms 
 */ 
async function sleep(ms) {
    await new Promise(resolve => {
        setTimeout(resolve, ms);
    });
}
  1. 変更対象のラベルとラベル名が一致したら、ボタンをクリックし、選択肢を画面上に表示させます。
  2. ボタンをクリックしてから選択肢が表示されるまで一瞬だけ時間がかかることがあります。
    そのまま処理を進めると、ノードの取得に失敗することがあるので、少しだけ待機します。

また、ボタンのクリックによってHTMLに選択肢候補のノードが追加されます。

<div class="actionBody">
    <records-record-layout-item field-label="優先度">
        <lightning-combobox>
            <label class="slds-form-element__label">
                優先度
            </label>
            <div lightning-combobox_combobox>
                <div lightning-basecombobox_basecombobox class="slds-dropdown-trigger_click">
                    <div lightning-basecombobox_basecombobox>
                        <button role="combobox" data-value="Medium" aria-label="優先度, Medium">
                        </button>
                    </div>
                    <!-- ここから -->
                    <div lightning-basecombobox_basecombobox>
                        <lightning-base-combobox-item data-value></lightning-base-combobox-item>
                        <lightning-base-combobox-item data-value="High"></lightning-base-combobox-item>
                        <lightning-base-combobox-item data-value="Medius"></lightning-base-combobox-item>
                        <lightning-base-combobox-item data-value="Low"></lightning-base-combobox-item>
                     </div>
                    <!-- ここまでノードが追加される -->
                </div>
            </div>
        </lightning-combobox>
    </records-record-layout-item>
</div>

選択肢の決定

            // 値の一致するオプションをクリックする。
            const options = item.parentNode.parentNode.parentNode.querySelectorAll('lightning-base-combobox-item'); // (1)
            for (let j = 0; j < options.length; j++) {
                const option = options[j];
                const val = option.getAttribute('data-value');
                if (val == option_value) {
                    option.click();
                }
            }
  1. 変数 item はbuttonノードです。そこからlightning-base-combobox-itemノードを取得するためには、それよりも上位のノード(class="slds-dropdown-trigger_click"のノード)まで.parentNodeをたどる必要があります。
  2. あとはdata-valueが一致するノードを選んでクリックをするだけです。

呼び出し処理

async function main() {
    await selectPulldown('優先度', 'High');
}

ラベル名と設定したい値を渡しています。

実行結果

右クリック後、値がHighに、背景が黄色に切り替わることを確認しましょう。

はまりやすいポイント

async-await

非同期処理が発生する場合は、メソッドの宣言にasyncを、呼び出し側にawaitを忘れずにつけます。awaitだけ書き忘れてsleepが効かないとハマることがよくあります。

salesforceにおけるノード参照

    const container = document.querySelector('.actionBody');
    const items = container.querySelectorAll('button[role="combobox"]');

今回、.actionBodyを持つノードを経由して2回に分けてプルダウンのノードを取得しています。本来であれば、下の行だけでノードが取得できるはずですが、経由有無によって以下のようにノードの取得結果が変わることを確認しています。

コンソールログから実行していますが、.actionBodyを経由していない方だけ要素が取得できていません。

根本原因は分かっていませんが、.actionBodyを経由することで解決はするので、良しとしましょう。