【Salesforce自動操作 第3回】テキスト入力の自動化【vba, chrome拡張機能】

目次

前回のおさらい

プルダウンの項目を指定した値で選択できるようになりました。

今回やること

同様に、単一行テキスト・テキストエリアに値を入力できるようにしていきます。

ソースコード 全体

/**
 * テキストボックス、テキストエリアに入力する
 * @param {string} target_label_name 画面上の表示ラベル名
 * @param {string} text 設定値
 */
async function inputText(target_label_name, text) {
    const container = document.querySelector('.actionBody');
    const items = container.querySelectorAll('input[part="input"], textarea.slds-textarea');
    for (let i = 0; i < items.length; i++) {
        const item = items[i];
        const label = item.parentNode.parentNode;
        if (label.innerText == target_label_name) {
            item.value = text;
        }
    }
}

async function main() {
    await selectPulldown('優先度', 'High');
    inputText('Web 会社名', 'ABCソリューション')
}
main();

 解説

ノード階層

単一行テキストのノード階層は以下の通り(例.Web会社名)

<div class="actionBody">
    <records-record-layout-item field-label="Web 会社名">
        <div part="input-text">
            <label part="label">Web 会社名</label>
        </div>
        <div part="input-controller">
            <input type="text" part="text" class="slds-input"/>
        </div>
    </records-record-layout-item>
</div>

テキストに値を設定するためにinputタグを、ラベル名を取得するためにlabelタグを取得します。

    const container = document.querySelector('.actionBody');
    const items = container.querySelectorAll('input[part="input"], textarea.slds-textarea');
  • .actionBodyノード経由で単一行テキストまたはテキストエリアを取得します。
        const label = item.parentNode.parentNode; // (1)
       if (label.innerText == target_label_name) { // (2)
            item.value = text;
        }
  1. labelタグを直接取得するのが手間であるため、labelタグの親ノードを取得しinnerTextでラベル名を取得します。
  2. 今回はテキストボックスがデフォルトで空白のため問題ありませんが、デフォルト値が空白でない場合innerTextが「ラベル名 + デフォルトのテキスト名」となるため、もう少し工夫する必要があります。

実行結果

結果の確認

無事にテキストに値を設定することが出来ました。わーわーぱちぱち。
…ではありません。

前回、Salesforceでは値が変わると項目の背景が黄色に切り替わると言いましたが、今回は白いままです。
つまり厳密には値が設定されていません。
試しに、保存ボタンを押して、登録されたケースを閲覧するとWeb 会社名の項目は空白のままです。

原因

Reactでは仮想DOMという技術を採用しています。
仮想DOMではDOMを二重管理していて、jsから参照できる片方のDOMを書き換えても、もう片方のDOMと同期がとれません。

SalesforceがReactを採用しているかは分かりませんが、同じような仕組みを取っているのだとは思います。Salesforceに限らず、レイアウトがリッチなサイトはとにかくスクレイピングが難しいです。
解決策は次回に回すとして、以降は私が悪あがき試したことを書いていきます。

テキスト入力の代替案

(1) focus

item.focus();
await sleep(100);
item.value = text;

focus自体は効きますが、やはり値が入力されたことにはなりませんでした。

(2) click

item.click();
await sleep(100);
item.value = text;

マウス操作の場合、click時にfocusが当たりますが、メソッド呼び出しの場合focusは当たりません。
focus > click > 代入でも同様です。

(3) dispatchEvent

let e = new KeyboardEvent( "keydown", { keyCode: 32 , bubbles: true}); // 32 : Spaceキー
item.dispatchEvent(e);

dispatchEventはaddEventListenerなどで事前に定義されたイベントを呼び出す機能です。
そのため、「KeyboardEventを発火させる」 =  「キーが入力される」ではありません。

仮に入力できたとしても、全角文字の入力はどうするのか?という問題があるため、実現不可です。

(4) execCommand

document.execCommand('copy')
document.execCommand('paste') 

mdn : Document: execCommand() メソッド
非推奨メソッドであるため不採用です。

(5) clipboard.readText

const text = await navigator.clipboard.readText();
item.value = text

クリップボードを経由することもできますが、最終的にはjavascriptでの値の代入に行きつくため、結局(1)focusと変わりません。

(6) winAPI

ソースコードは長くなるため、別記事にまとめています。(C#のコードしかありませんが)
ざっくりとした流れは以下の通りです。

  1. js:テキストボックスのスクリーン座標を計算する。
  2. js:スクリーン座標をローカルにcsv形式でダウンロードする。
  3. C#:ファイルから座標を読み取る。
  4. C#:指定座標をクリックする。(SetCursorPos, mouse_event
  5. C#:クリップボードに入力したい値を設定する。
  6. C#:貼り付けコマンド(ctrl + v)を実行する。(SendKeys.SendWait("^v")

デメリット

  • 動作が安定しない。うまくいく場合もあるが、体感打率3割。
    (クリックに失敗する、貼り付けコマンドで途中までしか文字が入らない等)
  • ファイル検知のため、常にC#のexeファイルを起動しておく必要がある。
    (地味に手間になる)
  • 待ち時間の設定が必要。js側ではいつ入力されるかが分からないため、最低でも2, 3秒待つ必要がある。
  • 座標依存である。chromeのバージョン?によってダウンロードされたファイル名が下バーに表示されるかマチマチである。それに伴いクリックすべき座標が変わり動作しない場合がある。

(1) ~ (5)とは違い唯一入力に成功したケースですが、あまりにもデメリットが大きすぎるため不採用です。

はまりやすいポイント

テキスト入力がうまくいかない場合がある。

原因や理由などはすでに記述したとおりです。次回の第4回でどう改善していくかを見ていきます。