fix(collect): recognize short Shopee product titles
This commit is contained in:
+145
-2
@@ -39,18 +39,23 @@ class FakeCDP:
|
||||
|
||||
|
||||
class FakeProductCDP:
|
||||
def __init__(self, ws, ready=True, toasts=None, rects=None):
|
||||
def __init__(self, ws, ready=True, toasts=None, rects=None, url=""):
|
||||
self.ws = ws
|
||||
self.ready = ready
|
||||
self.toasts = list(toasts or [])
|
||||
self.rects = list(rects or [])
|
||||
self.url = url
|
||||
self.closed = False
|
||||
self.sent = []
|
||||
self.evaluated = []
|
||||
self.toast_observer_installed = False
|
||||
|
||||
def val(self, expr):
|
||||
self.evaluated.append(expr)
|
||||
if expr == editor.JS_READY:
|
||||
return self.ready
|
||||
return json.dumps(self.ready) if isinstance(self.ready, dict) else self.ready
|
||||
if expr == "location.href":
|
||||
return self.url
|
||||
if expr == editor.JS_INSTALL_TOAST_OBSERVER:
|
||||
self.toast_observer_installed = True
|
||||
return True
|
||||
@@ -68,6 +73,32 @@ class FakeProductCDP:
|
||||
self.closed = True
|
||||
|
||||
|
||||
class FakeTitleCDP:
|
||||
def __init__(self, title="短标题"):
|
||||
self.title = title
|
||||
self.write_expr = ""
|
||||
|
||||
def val(self, expr):
|
||||
if expr == editor.JS_TITLE_STATE:
|
||||
return json.dumps(
|
||||
{
|
||||
"found": True,
|
||||
"candidate_count": 1,
|
||||
"source": "business",
|
||||
"field_exists": True,
|
||||
"value": self.title,
|
||||
"modelvalue": self.title,
|
||||
}
|
||||
)
|
||||
self.write_expr = expr
|
||||
marker = "s.call(el,"
|
||||
if marker in expr:
|
||||
encoded = expr.split(marker, 1)[1].split(");", 1)[0]
|
||||
self.title = json.loads(encoded)
|
||||
return self.title
|
||||
return None
|
||||
|
||||
|
||||
def cover_rects(count, prefix="old", start=0):
|
||||
return [
|
||||
{
|
||||
@@ -84,6 +115,26 @@ def cover_rects(count, prefix="old", start=0):
|
||||
]
|
||||
|
||||
|
||||
def ready_snapshot(**overrides):
|
||||
state = {
|
||||
"ready": True,
|
||||
"title_count": 1,
|
||||
"title_source": "business",
|
||||
"title_field_exists": True,
|
||||
"title_ambiguous": False,
|
||||
"image_field_exists": True,
|
||||
"image_manager_exists": True,
|
||||
"image_count": 5,
|
||||
"cdn_count": 5,
|
||||
"blob_count": 0,
|
||||
"upload_input_exists": True,
|
||||
"upload_input_count": 1,
|
||||
"current_url": "https://seller.shopee.tw/portal/product/48363984966",
|
||||
}
|
||||
state.update(overrides)
|
||||
return state
|
||||
|
||||
|
||||
class FakeUpdateCDP:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -636,6 +687,98 @@ class EditorLoginTests(unittest.TestCase):
|
||||
fake.sent,
|
||||
)
|
||||
|
||||
def test_editor_business_selectors_replace_title_length_guess(self):
|
||||
self.assertIn('data-product-edit-field-unique-id="name"', editor.TITLE_FIELD_SELECTOR)
|
||||
self.assertIn("data-product-edit-field-unique-id='name'", editor.TITLE_XPATH)
|
||||
self.assertNotIn("string-length", editor.TITLE_XPATH)
|
||||
self.assertIn("string-length", editor.LEGACY_TITLE_XPATH)
|
||||
self.assertIn('data-product-edit-field-unique-id="images"', editor.IMAGE_FIELD_SELECTOR)
|
||||
self.assertIn("data-product-edit-field-unique-id='images'", editor.ITEMBOX_XPATH)
|
||||
self.assertIn("source:'legacy'", editor.JS_EDITOR_FIELD_HELPERS)
|
||||
|
||||
def test_wait_ready_accepts_short_title_business_field_snapshot(self):
|
||||
fake = FakeProductCDP("ws-new", ready=ready_snapshot())
|
||||
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor._wait_ready(fake, timeout=0)
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertIn(editor.JS_SCROLL_MAIN_IMAGE_MANAGER, fake.evaluated)
|
||||
|
||||
def test_wait_ready_reports_missing_title_without_hidden_logistics_toast(self):
|
||||
state = ready_snapshot(ready=False, title_count=0)
|
||||
fake = FakeProductCDP(
|
||||
"ws-new",
|
||||
ready=state,
|
||||
url=state["current_url"],
|
||||
toasts=[
|
||||
{
|
||||
"text": "此物流選項不支援較長備貨商品",
|
||||
"url": state["current_url"],
|
||||
"visible": False,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
with self.assertRaises(editor.EditorError) as ctx:
|
||||
editor._wait_ready(fake, timeout=0)
|
||||
|
||||
message = str(ctx.exception)
|
||||
self.assertIn("未识别商品名称输入框", message)
|
||||
self.assertIn("商品图片5张", message)
|
||||
self.assertIn("主图上传入口正常", message)
|
||||
self.assertNotIn("物流選項", message)
|
||||
|
||||
def test_wait_ready_reports_ambiguous_business_title_inputs(self):
|
||||
fake = FakeProductCDP("ws-new", ready=ready_snapshot(ready=False, title_count=2))
|
||||
|
||||
with self.assertRaises(editor.EditorError) as ctx:
|
||||
editor._wait_ready(fake, timeout=0)
|
||||
|
||||
self.assertIn("商品名称输入框匹配到2个,无法确定唯一字段", str(ctx.exception))
|
||||
|
||||
def test_ready_error_appends_only_visible_same_page_toast_as_hint(self):
|
||||
url = "https://seller.shopee.tw/portal/product/48363984966"
|
||||
fake = FakeProductCDP(
|
||||
"ws-new",
|
||||
url=url,
|
||||
toasts=[{"text": "页面暂时繁忙", "url": url, "visible": True}],
|
||||
)
|
||||
|
||||
message = editor._open_product_ready_error(fake, "商品编辑器未就绪", current_url=url)
|
||||
|
||||
self.assertEqual("商品编辑器未就绪;页面提示(可能无关):页面暂时繁忙", message)
|
||||
self.assertIn("Object.assign({},item,{visible:false})", editor.JS_PAGE_TOASTS)
|
||||
|
||||
def test_read_and_change_title_share_business_field_locator(self):
|
||||
fake = FakeTitleCDP(title="二十字以内的有效短标题")
|
||||
|
||||
self.assertEqual("二十字以内的有效短标题", editor.read_title(fake))
|
||||
with mock.patch("app.editor.time.sleep"):
|
||||
result = editor.change_title(fake, "更新后的短标题")
|
||||
|
||||
self.assertTrue(result["ok"])
|
||||
self.assertEqual("business", result["source"])
|
||||
self.assertIn(editor.JS_EDITOR_FIELD_HELPERS, fake.write_expr)
|
||||
self.assertNotIn(editor.TITLE_XPATH, fake.write_expr)
|
||||
|
||||
def test_main_image_scripts_share_scoped_business_field_helpers(self):
|
||||
scripts = (
|
||||
editor.JS_READY,
|
||||
editor.JS_RECTS,
|
||||
editor.JS_UPLOAD_STATE,
|
||||
editor.JS_CLICK_UPLOAD_TILE,
|
||||
editor.JS_FIRST_COVER,
|
||||
editor.JS_SCROLL_MAIN_IMAGE_MANAGER,
|
||||
editor.JS_MAIN_UPLOAD_INPUT,
|
||||
editor.JS_DISPATCH_MAIN_UPLOAD_INPUT_EVENTS,
|
||||
editor.JS_CLICK_FIRST_DELETE,
|
||||
)
|
||||
for script in scripts:
|
||||
with self.subTest(script=script[:40]):
|
||||
self.assertIn(editor.JS_EDITOR_FIELD_HELPERS, script)
|
||||
self.assertIn(json.dumps(editor.IMAGE_FIELD_SELECTOR), script)
|
||||
|
||||
def test_wait_ready_raises_product_unavailable_from_hidden_toast(self):
|
||||
fake = FakeProductCDP(
|
||||
"ws-new",
|
||||
|
||||
Reference in New Issue
Block a user