Web: TextField values set through the DOM (autofill-like) are lost; on_submit carries an empty value

オープン
#7,056 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
48/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
flutter, playwright, python
領域
frontend, mobile

調査の方向性

Start by reproducing the issue with the supplied app.py and run_arms.py on Flet web, then trace how Flutter web semantics inputs sync DOM values back to TextField. Compare the autofill-like Locator.fill path with the working typed-input path, especially around Enter submission. Done means DOM-entered values reliably reach TextField.value and on_submit across the reported viewports.

索引モデルが issue の本文から書いたものです。

説明

Flet / flet-web: 1.0.5 (stock, no other code). Browser: Chromium headless shell (Playwright 1.64.1), macOS.
Mode: web (ft.run(main, port=8771), served to the browser).

App

One page: an email TextField, a password TextField(password=True) and a Button("Log In"). on_submit on both
fields and on_click on the button call one handler that prints the event's source, whether e.data is empty, and
whether each field's .value is empty (never the text). The full app.py and the Playwright script (run_arms.py) are below. Run: python app.py > server.log, then python run_arms.py 10 L1,L2 in the same directory.

Steps (n=10 per viewport, fresh browser context each run)

  • Control, L1: click the email field, type the email; click the password field, type the password; press Enter.
  • L2 (autofill-like): enable semantics by clicking flt-semantics-placeholder (Flutter web builds no <input>
    before that), then Playwright Locator.fill on flt-semantics input[type=text] and input[type=password], with
    no click on the fields; press Enter.

Result (60 runs, inputs found in 60/60)

viewport L1 OK L2 OK L2 value lost
1280x800 10/10 3 7
390x844 (mobile, touch) 10/10 6 4
844x390 (mobile, touch) 10/10 3 7

In every one of the 18 lost runs the password <input>'s DOM value was already empty when Enter was pressed (read with
JS), and the server received one on_submit from the password field with e.data empty and both fields' .value
empty. Enter always produced an event (none of the 60 runs went without one). The loss looks time-dependent: early
runs mostly lost the values, later runs at 390x844 mostly kept them. It looks as if a later semantics/controller
resync overwrites values that the DOM received directly; we did not measure further.

Expected

A value placed in the field's <input> (as password managers and browser autofill do) is kept and reaches
TextField.value and on_submit's e.data.

Caveat

L2 enables semantics with a script click on the placeholder; a real autofill path may enable it differently.

app.py

import sys, time
import flet as ft

def main(page: ft.Page):
    page.title = "Repro"
    email = ft.TextField(label="Email", autofill_hints=[ft.AutofillHint.EMAIL])
    password = ft.TextField(label="Password", password=True, can_reveal_password=False,
                            autofill_hints=[ft.AutofillHint.PASSWORD])

    def handler(source):
        def h(e):
            data = getattr(e, "data", None)
            ne = lambda v: "nonempty" if v else "empty"
            print(f"EVENT source={source} data={ne(data)} email_value={ne(email.value)} "
                  f"password_value={ne(password.value)} t={time.monotonic():.3f}", flush=True)
        return h

    email.on_submit = handler("email")
    password.on_submit = handler("password")
    btn = ft.Button("Log In", on_click=handler("button"))
    page.add(ft.Column([email, password, btn]))
    print(f"SESSION_READY t={time.monotonic():.3f}", flush=True)

ft.run(main, view=None, port=8771)

run_arms.py

import json, time, os, sys
from playwright.sync_api import sync_playwright
HERE = os.path.dirname(os.path.abspath(__file__))
LOG = os.path.join(HERE, 'server.log')
URL = 'http://127.0.0.1:8771/'
VPS = [("1280x800", dict(viewport={'width':1280,'height':800})),
       ("390x844", dict(viewport={'width':390,'height':844}, is_mobile=True, has_touch=True)),
       ("844x390", dict(viewport={'width':844,'height':390}, is_mobile=True, has_touch=True))]
N = int(sys.argv[1]) if len(sys.argv) > 1 else 10
ARMS = sys.argv[2].split(',') if len(sys.argv) > 2 else ['L1', 'L2']
EMAIL = "[email protected]"; PW = "example-password-123"   # dummy values

def loglines():
    return open(LOG).read().splitlines()

def pw_state(pg):
    return pg.evaluate("""()=>{const i=Array.from(document.querySelectorAll('input')).filter(e=>e.type==='password');
      if(!i.length) return 'noinput'; return i.some(e=>e.value&&e.value.length>0)?'nonempty':'empty'}""")

def one(browser, vp, arm):
    ctx = browser.new_context(**vp); pg = ctx.new_page()
    n0 = len(loglines())
    pg.goto(URL)
    pg.wait_for_selector('flt-semantics-placeholder', state='attached', timeout=30000)
    t = time.time()
    while time.time()-t < 30 and not any(l.startswith('SESSION_READY') for l in loglines()[n0:]):
        time.sleep(0.2)
    time.sleep(1.0)
    found = False; how = ''
    if arm == 'L2':
        pg.mouse.click(2, 2)
        # semantics must be enabled to create DOM inputs; click on placeholder via JS (no field click)
        pg.evaluate("document.querySelector('flt-semantics-placeholder')&&document.querySelector('flt-semantics-placeholder').click()")
        how = 'mouse.click(2,2); placeholder.click() via JS to build semantics; fill on flt-semantics input[aria-label]'
        try:
            pg.wait_for_selector('flt-semantics input[type=password]', timeout=30000); found = True
        except Exception: found = False
        if found:
            pg.locator('flt-semantics input[type=text]').first.fill(EMAIL)
            pg.locator('flt-semantics input[type=password]').first.fill(PW)
    else:
        how = 'canvas coordinate clicks (no semantics enabled), keyboard.type'
        # layout is fixed: email field centre ~(160,34), password ~(160,92)
        pg.mouse.click(160, 34); time.sleep(0.5)
        found = pg.evaluate("document.querySelectorAll('input').length")>0
        pg.keyboard.type(EMAIL, delay=15)
        pg.mouse.click(160, 92); time.sleep(0.5)
        pg.keyboard.type(PW, delay=15)
    time.sleep(0.5)
    pws = pw_state(pg)
    if arm == 'L1': found = found and pws != 'noinput'
    n1 = len(loglines())
    pg.keyboard.press('Enter')
    t = time.time(); evs = []
    while time.time()-t < 10:
        evs = [l for l in loglines()[n1:] if l.startswith('EVENT')]
        if evs and time.time()-t > 3: break
        time.sleep(0.25)
    if not evs: cls = 'NO_FRAME'
    elif any('password_value=nonempty' in l or 'data=nonempty' in l for l in evs): cls = 'OK'
    else: cls = 'DROP'
    ctx.close()
    return dict(found=found, dom_password_at_enter=pws, events=evs, cls=cls, how=how)

with sync_playwright() as p:
    b = p.chromium.launch(headless=True)
    out = open(os.path.join(HERE, 'runs.jsonl'), 'a')
    for name, vp in VPS:
        for arm in ARMS:
            for i in range(N):
                try: r = one(b, vp, arm)
                except Exception as e: r = dict(found=False, dom_password_at_enter='err', events=[], cls='NO_FRAME', how='exception '+type(e).__name__)
                r.update(viewport=name, arm=arm, run=i)
                out.write(json.dumps(r)+'\n'); out.flush()
                print(name, arm, i, r['found'], r['dom_password_at_enter'], r['cls'], flush=True)
    b.close()
主要言語
Python
スター
17.5k
フォーク
740
平均マージ
1日 8時間
マージ済み PR(30日)
74

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

flet-dev/flet のほかの issue

flet-dev/flet の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。