Selenium×Python|set_window_size・maximize_windowが効かない原因と解決策【ヘッドレス非使用のクリック不可を最短で直す】

Python+Seleniumで「ヘッドレスではないのに set_window_size()maximize_window() が効かず、画面外の要素がクリックできない」という相談は珍しくありません。本稿では“なぜサイズ変更が反映されないのか”を原因別に分解し、確実にウィンドウやビューポートを制御するための実装レシピと、クリック失敗を根治するための総合対策をまとめます。貼り付けて動く診断スクリプトも用意しました。

目次

現象の要点(症状のすり合わせ)

  • ヘッドレスではない実行だが、driver.set_window_size(1920,1080) が反映されない/サイズが変わらない。
  • driver.maximize_window()driver.fullscreen_window() も期待どおりに働かず、要素がビューポート外に残る。
  • クリック時に ElementClickInterceptedExceptionMoveTargetOutOfBoundsExceptionElementNotInteractableException 等が発生する。
  • 回避として scrollIntoView() や JS クリックで凌いでいるが、本来はウィンドウ/ビューポートの制御で解決したい。

先に結論(最短ルート)

  1. 呼び出しタイミングを修正:最初のナビゲーション(get())後に set_window_rect() を使う。
  2. “ウィンドウ”ではなく“ビューポート”を合わせる:JS で innerWidth/innerHeight を読み取り、差分を補正してピクセル精度で調整。
  3. オプションで初期値を固定--start-maximized--window-size=1920,1080同時に与えてプロファイル復元の影響を排除。
  4. Chrome限定のCDP手法Browser.setWindowBounds で OS 由来の制限をバイパス。
  5. 環境起因の阻害要因を除去:ドライバ不整合・DPI/スケーリング・Wayland/Xvfb・拡張機能・ユーザープロファイル復元等を片付ける。

原因別チェックリスト(Quick Fix表)

主な原因よくある兆候即効の対処
ブラウザとWebDriverのメジャー不整合ウィンドウ操作系が無反応/例外なしで無視Chrome×ChromeDriver、Firefox×GeckoDriver のメジャーを一致させる
呼び出しタイミングが早い(ウィンドウ未確定)driver = Chrome()直後のサイズ指定が無視driver.get("about:blank") 直後に set_window_rect()
“ウィンドウサイズ”と“ビューポート”の混同外枠は変わるが innerWidth/Height が狙い値でないJSで差分を計測し、補正して再設定
OSのDPI/スケーリング(高DPI/Retina)クリック位置がずれる/座標系が不安定OSスケール100%推奨。Chromeに--force-device-scale-factor=1
プロファイル復元や拡張機能が干渉毎回同じ位置・サイズに戻るクリーンプロファイル(拡張なし)で実行
WaylandやXvfb等のウィンドウマネージャ依存Linuxで最大化が効かない/Docker内で無反応WM導入(fluxbox等)/--ozone-platform=x11/Xvfbの画面サイズ拡張
マルチモニタの負座標・オフスクリーン配置位置は変わるが要素が見切れるset_window_position(0,0) の併用
フルスクリーン(F11)やスナップの残留最大化しても縦が足りないfullscreen_window() 切替→解除→set_window_rect()
古いヘッドレスの固定ビューポートヘッドレス時だけ800×600相当--headless=new--window-size=… を必ず付与
Edge/Firefoxの挙動差ブラウザにより成功/失敗が変わるset_window_rect を優先。Firefoxは特に有効
kiosk/appモードの誤用常に全画面・サイズ変更不能起動オプションから --app/--kiosk を除去
ズーム≠解像度問題ズーム90%/110%でクリックが逸れるズーム100%固定(Ctrl+0相当を送る等)

基本レシピ:まずはこの3ステップ

  1. クリーンプロファイルで起動(拡張・復元なし)。
  2. 最初のナビゲーション後に set_window_rect を使って位置と大きさを同時に決める。
  3. ビューポートの実値innerWidth/innerHeight)を読み取り、必要なら補正する。

実装例(Chrome)

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def new_driver():
opts = Options()
# クリーン&安定のための最低限
opts.add_argument("--disable-extensions")
opts.add_argument("--disable-infobars")
opts.add_argument("--start-maximized")          # OS依存の最大化
opts.add_argument("--window-size=1920,1080")    # 復元・初期化の揺れ止め
# 高DPIでの座標ズレ対策(必要に応じて)
# opts.add_argument("--force-device-scale-factor=1")
driver = webdriver.Chrome(options=opts)
return driver

driver = new_driver()
driver.get("about:blank")  # ウィンドウが確定するポイント

# 位置とサイズを一発で指定(最大化で効かない環境にも強い)

driver.set_window_rect(x=0, y=0, width=1920, height=1080)

# 実際のビューポートを確認

vw, vh = driver.execute_script("return [window.innerWidth, window.innerHeight];")
print("viewport:", vw, vh) 

ビューポートをピクセル精度で合わせる(決定版)

テストで必要なのは「ウィンドウ外枠のサイズ」ではなくビューポート(window.innerWidth/Heightです。以下のヘルパは、外枠と内側差分を自動補正し、指定したビューポートに合わせます。

def set_viewport_size(driver, width, height, max_attempts=5):
    """
    ビューポート(=window.innerWidth/innerHeight)を指定ピクセルに合わせる。
    ウィンドウ枠の差分を計測して補正する。
    """
    for _ in range(max_attempts):
        # 現在のwindow/viewportを取得
        rect = driver.get_window_rect()
        outer_w, outer_h = rect["width"], rect["height"]
        inner_w, inner_h = driver.execute_script(
            "return [window.innerWidth, window.innerHeight]"
        )
        # 目標との差
        dw, dh = width - inner_w, height - inner_h
        if abs(dw) <= 1 and abs(dh) <= 1:
            return
        driver.set_window_rect(
            x=rect["x"],
            y=rect["y"],
            width=max(100, outer_w + dw),
            height=max(100, outer_h + dh),
        )
    # 最後に念のためもう一度読み直す
    inner_w, inner_h = driver.execute_script(
        "return [window.innerWidth, window.innerHeight]"
    )
    print(f"final viewport: {inner_w}x{inner_h}")

# 使い方

driver.get("[https://example.com](https://example.com)")
set_viewport_size(driver, 1366, 768) 

CDPでウィンドウ境界を直接いじる(Chrome限定・強力)

OSやWMの都合で最大化やサイズ変更が抑止される場合、Chrome DevTools Protocol(CDP)経由でウィンドウ境界を直接設定すると改善します。

# ウィンドウIDを取得し、境界を「normal」+明示サイズに
info = driver.execute_cdp_cmd("Browser.getWindowForTarget", {"targetId": None})
win_id = info["windowId"]
driver.execute_cdp_cmd("Browser.setWindowBounds", {
    "windowId": win_id,
    "bounds": {"left": 0, "top": 0, "width": 1920, "height": 1080, "windowState": "normal"}
})

Firefox(GeckoDriver)での要点

  • set_window_rect() が最も確実。Firefoxでは maximize_window() より安定する場面が多い。
  • ヘッドレス時は options.headless = True に加え、必ず --width--height(または --window-size 相当)を指定。
from selenium.webdriver.firefox.options import Options as FirefoxOptions
opts = FirefoxOptions()
# opts.headless = True  # ヘッドレス時
driver = webdriver.Firefox(options=opts)
driver.get("about:blank")
driver.set_window_rect(0, 0, 1920, 1080)

ヘッドレス運用の補足

ヘッドレスでは“ウィンドウ”の概念が薄く、--window-size=W,H でビューポートを決めるのが確実です。新しい実装(新ヘッドレス)を使う場合は以下のようにします。

from selenium.webdriver.chrome.options import Options
opts = Options()
opts.add_argument("--headless=new")       # 新ヘッドレス
opts.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=opts)

クリック失敗を根治する堅牢な操作パターン

サイズ制御に加えて、DOM変化・固定ヘッダ・アニメーションによる「要素クリック不可」を減らす実用パターンを示します。

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait as Wait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys

def safe_click(driver, locator, timeout=20):
el = Wait(driver, timeout).until(EC.element_to_be_clickable(locator))
# 固定ヘッダ回避のため、中央より少し下に来るようスクロール
driver.execute_script(
"arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});", el
)
# スクロールの慣性が止むまで待つ
Wait(driver, timeout).until(lambda d: d.execute_script("return document.readyState") == "complete")
try:
el.click()
except Exception:
# 最後の手段:アクションチェーンで移動→クリック
ActionChains(driver).move_to_element(el).pause(0.05).click().perform()

# 使い方

safe_click(driver, (By.CSS_SELECTOR, "button.buy")) 

実行環境別の落とし穴と対処

環境起こりがちな問題対策
Windows(高DPI)スケーリングに伴う座標ズレOSスケール100%/--force-device-scale-factor=1/ビューポート補正
macOS(フルスクリーン)フルスクリーン状態で最大化が効かないfullscreen_window() 切替→解除→set_window_rect()
Linux(Wayland)最大化や移動をWMが制御--ozone-platform=x11 でX11に、またはWM導入
Docker/Xvfb画面サイズがVNC/Xvfbの解像度に制限Xvfb起動時に -screen 0 1920x1080x24 指定、WM導入
リモートデスクトップ接続先の実画面サイズに依存RDP解像度を十分に確保/CDP境界設定
ユーザープロファイル指定前回終了時のサイズが復元クリーンプロファイル or --start-maximized--window-size

貼り付けて使える診断スクリプト

現状を数値で把握すると原因切り分けが一気に進みます。以下は Chrome 用の簡易診断。

import platform
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

opts = Options()
opts.add_argument("--disable-extensions")
opts.add_argument("--start-maximized")
opts.add_argument("--window-size=1600,900")
driver = webdriver.Chrome(options=opts)

driver.get("about:blank")

def dump():
rect = driver.get_window_rect()
outer = (rect["width"], rect["height"])
inner = driver.execute_script("return [window.innerWidth, window.innerHeight]")
dpr  = driver.execute_script("return window.devicePixelRatio")
zoom = driver.execute_script("return document.body ? (document.body.style.zoom || '1') : '1'")
print("OS:", platform.platform())
print("outer:", outer, "inner:", inner, "dpr:", dpr, "zoom:", zoom)

print("=== before ===")
dump()

driver.set_window_rect(0, 0, 1920, 1080)
print("=== after set_window_rect ===")
dump()

# CDPで境界を直接変更(Chrome限定)

info = driver.execute_cdp_cmd("Browser.getWindowForTarget", {"targetId": None})
driver.execute_cdp_cmd("Browser.setWindowBounds", {
"windowId": info["windowId"],
"bounds": {"left": 0, "top": 0, "width": 1920, "height": 1080, "windowState": "normal"}
})
print("=== after CDP bounds ===")
dump() 

安定運用のベストプラクティス

  • サイズ決定の順序を固定:起動→get("about:blank")set_window_rect()→(必要に応じ set_viewport_size())。
  • ビューポート前提で考える:解像度の“見た目”ではなく window.innerWidth/innerHeight を基準にテストを設計。
  • ズームは常に100%:ズーム変更は見かけのピクセル比を変えるため、安定性を損なう。
  • クリーンプロファイル:拡張や復元は不確定要素。CIでは都度新規プロファイルを生成。
  • 待機の質を上げる:サイズが決まっても、レイアウトシフト(CLS)中はクリック失敗が出る。EC.element_to_be_clickablescrollIntoView({block:'center'}) をセットで。
  • スクリーンショットで可視化:定期的に get_screenshot_as_file() で保存し、失敗時の“視覚的な証拠”を残す。

よくある質問(FAQ)

maximize_window()fullscreen_window() の違いは?」 前者はOSの“最大化”機能(タスクバー等は残る)、後者はF11相当で「フルスクリーン」。UIの有無やワークエリアの扱いが異なります。テストでは set_window_rect() による明示指定が最も再現性が高いです。

<dt>「<code>set_window_size()</code> より <code>set_window_rect()</code> が推奨なのはなぜ?」</dt>
<dd><code>set_window_rect()</code> は位置とサイズを同時に更新でき、マルチモニタや負座標の影響を排除しやすいためです。内部実装もより新仕様に沿っています。</dd>

<dt>「ヘッドレスではないのにサイズが固定される」</dt>
<dd>ユーザープロファイルの復元・kiosk/appモード・ウィンドウマネージャの制約が典型です。クリーンプロファイル+<code>--start-maximized</code>+<code>--window-size</code>+<code>set_window_rect()</code> の併用でほぼ解決します。</dd>

<dt>「クリックはできるが、要素がヘッダに隠れて反応しない」</dt>
<dd><code>scrollIntoView({block:'center'})</code> を使って“中央寄せ”スクロールにし、固定ヘッダを避けます。それでも被る場合は <code>element_from_point</code> で前面要素を検査し、必要ならオフセットクリックを行います。</dd>

サンプル:総合テンプレート(貼り付けて使う)

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait as Wait
from selenium.webdriver.support import expected_conditions as EC

def create_driver(w=1920, h=1080, scale1=False):
opts = Options()
opts.add_argument("--disable-extensions")
opts.add_argument("--disable-infobars")
opts.add_argument("--start-maximized")
opts.add_argument(f"--window-size={w},{h}")
if scale1:
opts.add_argument("--force-device-scale-factor=1")
return webdriver.Chrome(options=opts)

def set_viewport_size(driver, width, height):
for _ in range(6):
rect = driver.get_window_rect()
iw, ih = driver.execute_script("return [window.innerWidth, window.innerHeight]")
dw, dh = width - iw, height - ih
if abs(dw) <= 1 and abs(dh) <= 1:
return
driver.set_window_rect(rect["x"], rect["y"], rect["width"] + dw, rect["height"] + dh)

def open_and_prepare(driver, url, vw=1366, vh=768):
driver.get("about:blank")                   # ウィンドウ確定
driver.set_window_rect(0, 0, 1920, 1080)    # まずは広く確保
driver.get(url)
set_viewport_size(driver, vw, vh)           # 目標ビューポートに調整

def click_centered(driver, locator, timeout=20):
el = Wait(driver, timeout).until(EC.element_to_be_clickable(locator))
driver.execute_script("arguments[0].scrollIntoView({block:'center'});", el)
el.click()

# 使い方例

driver = create_driver()
open_and_prepare(driver, "[https://example.com](https://example.com)", vw=1366, vh=768)
click_centered(driver, (By.CSS_SELECTOR, "button#submit")) 

まとめ

ポイントは「ウィンドウ」ではなく「ビューポート」を制御する発想に切り替えることです。
1) クリーンプロファイルで起動し、2) get() 後に set_window_rect() で大枠を決め、3) innerWidth/Height を読み取って差分補正する。この三段構えにより、OS・WM・DPI・プロファイル復元といった環境差をまたいで、クリック不能の根本原因を潰せます。
さらに、CDPでの境界設定や固定ヘッダを考慮した scrollIntoView({block:'center'}) の運用を組み合わせれば、テストは大幅に安定します。ここで紹介したテンプレートと診断スクリプトを土台に、プロジェクト固有の最適解へ調整してください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次