Python+Seleniumで「ヘッドレスではないのに set_window_size() や maximize_window() が効かず、画面外の要素がクリックできない」という相談は珍しくありません。本稿では“なぜサイズ変更が反映されないのか”を原因別に分解し、確実にウィンドウやビューポートを制御するための実装レシピと、クリック失敗を根治するための総合対策をまとめます。貼り付けて動く診断スクリプトも用意しました。
現象の要点(症状のすり合わせ)
- ヘッドレスではない実行だが、
driver.set_window_size(1920,1080)が反映されない/サイズが変わらない。 driver.maximize_window()やdriver.fullscreen_window()も期待どおりに働かず、要素がビューポート外に残る。- クリック時に
ElementClickInterceptedException、MoveTargetOutOfBoundsException、ElementNotInteractableException等が発生する。 - 回避として
scrollIntoView()や JS クリックで凌いでいるが、本来はウィンドウ/ビューポートの制御で解決したい。
先に結論(最短ルート)
- 呼び出しタイミングを修正:最初のナビゲーション(
get())後にset_window_rect()を使う。 - “ウィンドウ”ではなく“ビューポート”を合わせる:JS で
innerWidth/innerHeightを読み取り、差分を補正してピクセル精度で調整。 - オプションで初期値を固定:
--start-maximized/--window-size=1920,1080を 同時に与えてプロファイル復元の影響を排除。 - Chrome限定のCDP手法:
Browser.setWindowBoundsで OS 由来の制限をバイパス。 - 環境起因の阻害要因を除去:ドライバ不整合・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ステップ
- クリーンプロファイルで起動(拡張・復元なし)。
- 最初のナビゲーション後に
set_window_rectを使って位置と大きさを同時に決める。 - ビューポートの実値(
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_clickableとscrollIntoView({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'}) の運用を組み合わせれば、テストは大幅に安定します。ここで紹介したテンプレートと診断スクリプトを土台に、プロジェクト固有の最適解へ調整してください。

コメント