LoginSignup
3

More than 1 year has passed since last update.

【Node.js、Javascript】Seleniumで要素のXPathを一気に取得する。

Posted at

はじまり

一般的にDOMから要素のXPathを取得する方法は、ブラウザの開発者ツールからとされています。
しかし、Seleniumで要素にアクセスするためにいちいちDOMを右クリックしてXPathを取得するのも面倒だと思います。僕の場合は、300回右クリックしてXPathをメモするのは嫌だったので、一気にXPathを取得する方法を探しました。
今回は、そのときのソースを掲載します。

ソース

今回の方法では、2つファイルを作り実現しました。
作るファイルは、ブラウザで実行させるスクリプトが入ったファイルと、普通にseleniumを実行するファイルです。
Chromeで実行し、対象のCSSクラス名はcheckboxでした。

まず、ブラウザ側のファイルとなります。
最後の方は文法としては変ですが、配列のインデックスをスクリプトに載せるために"scriptSeparator"と記述しています。

ブラウザ側のファイル.js
function getXpathByElement (element) {
    var NODE_TYPE_ELEMENT_NODE = 1;

    if (element instanceof Array) {
        element = element[0];
    }

    if (element.nodeType != NODE_TYPE_ELEMENT_NODE) {
        throw new ErrorException('nodes other than the element node was passed. node_type:'+ element.nodeType +' node_name:'+ element.nodeName);
    }

    var stacker = [];
    var node_name = '';
    var node_count = 0;
    var node_point = null;

    do {
        node_name = element.nodeName.toLowerCase();
        if (element.parentNode.children.length > 1) {
            node_count = 0;
            node_point = null;
            for (i = 0;i < element.parentNode.children.length;i++) {
                if (element.nodeName == element.parentNode.children[i].nodeName) {
                    node_count++;
                    if (element == element.parentNode.children[i]) {
                        node_point = node_count;
                    }
                    if (node_point != null && node_count > 1) {
                        node_name += '['+ node_point +']';
                        break;
                    }
                }
            }
        }
        stacker.unshift(node_name);
    } while ((element = element.parentNode) != null && element.nodeName != '#document');

    return '/' + stacker.join('/').toLowerCase();
}

return getXpathByElement(document.getElementsByClassName("checkbox")["scriptSeparator"]);

そして、seleniumを実行するファイルです。

Selenium用ファイル.js
const { Builder, By } = require('selenium-webdriver');
const fs     = require("fs");
const _      = require("lodash");

const getTextLines = (fileName) => {
    const separator = '\n';
    let text = fs.readFileSync(`./${fileName}`, 'utf8');
    let lines = text.toString().split(separator);
    return lines;
}

const getXpathsByClassName = async(driver, className) => {
    let elements = [];
    let xpath    = '';
    let xpaths   = [];
    const scriptFileName = 'lib/getXpathByElement.js';
    let script  = await getTextLines(scriptFileName).join('\t');
    let scripts = await script.toString().split('\"scriptSeparator\"');
    elements = await driver.findElements(By.className(className));
    for (let i = 0; i < elements.length; i++){
        xpath = await driver.executeScript(`${scripts[0]}${i}${scripts[1]}`);
        xpaths.push(_.cloneDeep(xpath));
    }
    return new Promise( resolve => resolve(xpaths) );
};

let driver;
describe("テスト", () => {
    before(() => {
        driver = new Builder().forBrowser("chrome").build();
    });

    after(() => {
        return driver.quit();
    });

    it{(`URLを開いたり、対象の要素を画面上に表示する。`), async () => {

        // URLを開いたり、対象の要素を画面上に表示する処理

    }};

    it(`XPath取得`, async () => {
        xpathArray = await getXpathsByClassName(driver, 'checkbox');
    });
});

他に試した方法

driver.findElements(By.className(className))で取得した要素をexecuteScriptに渡す。

ブラウザ側で認識している要素とどうやら違うようで、返り値がすべてnullになったので、失敗。

ブラウザ側でXPathの配列を作る。

メモリ不足エラーでブラウザが止まり、失敗。

おしまい

ブラウザ側のファイルはこちらを参考にしています。とても助かりました!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3