Help us understand the problem. What is going on with this article?

selenium-webdriver(node版)でclosest要素を取得する

More than 1 year has passed since last update.

何がしたいか。

Element.closest的なことをseleniumでやりたい時の対応

ul.books
  li
    .book
      .name
        a href="http://path/to/buy" 罪と罰
      .outline
        p ほげほげほげほげ
        p ほげほげほげ
        a href="http://path/to/more" もっと読む
  li
    .book
      .name
        a href="http://path/to/buy" ダヴィンチ・コード
      .outline
        p ほげほげほげほげ
        p ほげほげほげ
        a href="http://path/to/more" もっと読む
  li
    .book
      .name
        a href="http://path/to/buy" 白鯨
      .outline
        p ほげほげほげほげ
        p ほげほげほげ
        a href="http://path/to/more" もっと読む

みたいな要素があってダヴィンチ・コードの[もっと読む]をクリックしたい時とかに

let anchor = await driver.findElement(By.partialLinkText('ダヴィンチ・コード'));
await anchor.closest('.book').findElement(By.partialLinkText('もっと読む')).click();

みたいなことをしたいけどWebElement.prototype.closestなんてものはないのでできない

方法

selenium-webdriverのNode版のLocatorにはBy.jsとかいうのがある。
これを使うと、与えたscriptをexecuteScriptでブラウザ側で実行して、取得したDOMをWebElementとして扱える。
なので、executeScriptで与えるscript内でネイティブのElement.closestすればよい。

実装例

By.jsの第一引数はクライアント側で実行するscript, 第二引数以降はscriptに渡す引数となる。
第二引数でダヴィンチ・コードのリンクを渡して、それをscriptが拾ってclosestで要素をとればclosest要素がとれる。

let bookAnchor = await driver.findElement({partialLinkText: 'ダヴィンチ・コード'});
let book = await driver.findElement(
  By.js(
    el => el.closest('.book'),
    bookAnchor
  )
);
await book.findElement({partialLinkText: 'もっと読む'}).click();

もし実行対象のブラウザがElement.closestに対応してない場合はscript内でparentNodeをたどって対象のdomになるまで探せば良い。

do {
  if (el.matches && el.matches(selector)) {
    return el;
  }
} while (el = el.parentNode)
// なければ例外
throw new Error('closest target element is not found');

おまけ By.jsについて

あんまりドキュメントに詳しく使い方書かれてないけど実装みるとBy.jsはこんな感じの実装になってる

selenium-webdriver/lib/by.js
static js(script, var_args) {
  let args = Array.prototype.slice.call(arguments, 0);
  return function(driver) {
    return driver.executeScript.apply(driver, args);
  };
}

locator実行時に第一引数をdriverとして受け取って、executeScriptしてる。
ちなみにexecuteScriptWebDriver.prototypeにはあるけどWebElement.prototypeにはない。
findElement系では現在のcontextをlocatorに渡すのでBy.jsを与えるのはWebElement.prototype.findElementではなくてWebDriver.prototype.findElementでなければいけないのでその点は注意が必要。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away