Submit a TYFCB or One-to-One Slip

Site bniconnectglobal.comTask submit-tyfcb-slipVersion v20Updated Aug 13, 2026Category browser-automation

Fill or submit authenticated TYFCB and One-to-One slips in BNI Connect Global using context-qualified controls, exact Material UI option matching, React-safe field updates, and post-action verification. This skill was captured from a live agent session on bniconnectglobal.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Fill or submit an authenticated TYFCB or One-to-One slip on BNI Connect Global. Support placeholders {member-name}, {inviter-name}, {location}, {date}, and {topic} for One-to-One entries. Fill-only requests must stop after verification; submit only when the caller explicitly requests submission.

Use Cases

Use for TYFCB or One-to-One form inspection, filling, or confirmed submission. Do not use for internal referrals, cross-chapter member lookup, or reviewing existing slips.

Automation Flow

  1. If authentication is required, navigate to https://www.bniconnectglobal.com/login, fill input[name="username"] and input[name="password"] with runtime credentials, activate button[type="submit"], and wait for the authenticated /web/ portal. Never retain credentials or expose transient ;jsessionid values.
  2. No stable direct form URL is known. On the authenticated dashboard, for One-to-One activate only the visible exact 提交 control whose nearby context matches 一對一會面, 一對一, 一对一, One-to-One, or 1-2-1, excluding 提供引薦 and 分會教育. For TYFCB, activate the visible Submit TYFCB control qualified by referral/TYFCB context. Wait for the rendered form or div[role="dialog"].
  3. For One-to-One, open the member .MuiSelect-select or [role="combobox"] associated with member/recipient by dispatching bubbling mousedown and calling click(). Wait for ul[role="listbox"] > li[role="option"], then run the evaluator below with {member-name} and select=true. Select only an enabled option whose normalized visible text exactly matches the requested name.
  4. Reopen the current inviter select identified by 邀請人, 邀請者, Inviter, or invite, wait for its current listbox options, and run the same evaluator with {inviter-name} and select=true. Do not assume member and inviter options share a list or ordinal.
  5. Run the One-to-One fill-and-verify evaluator below with {member-name}, {inviter-name}, {location}, {date}, and {topic}. It identifies fields by labels/context, uses React-safe native setters for location/date/topic, and verifies the actual values. Resolve “today” at runtime and verify the resulting date value rather than assuming a format.
  6. For fill-only requests, stop at allMatch:true and do not activate Save, Submit, or another action control. For an explicitly confirmed submission, re-run verification, qualify an enabled action control within the same verified form, activate it, wait for visible success/result evidence, and run the submission-result extractor. Dialog closure or navigation alone is insufficient proof.
  7. For TYFCB, use the same exact enabled listbox selection technique, then fill and verify {recipient-name}, {amount}, {classification}, {status}, and optional {comment} before any explicitly confirmed submission.

Exact option evaluator:

((wantedName = "", select = false) => {
  const v = (e) =>
      !!e && (e.offsetWidth || e.offsetHeight || e.getClientRects().length),
    c = (s) => (s || "").replace(/\s+/g, " ").trim(),
    q = c(wantedName).toLocaleLowerCase(),
    els = [
      ...document.querySelectorAll('ul[role="listbox"] > li[role="option"]'),
    ].filter(v),
    opts = els.map((e, i) => ({
      index: i,
      text: c(e.innerText || e.textContent),
      value: e.getAttribute("data-value") || e.getAttribute("value") || null,
      disabled:
        e.getAttribute("aria-disabled") === "true" ||
        e.classList.contains("Mui-disabled"),
      selected: e.getAttribute("aria-selected") === "true",
    })),
    m = opts.filter((x) => x.text.toLocaleLowerCase() === q),
    chosen = m.find((x) => !x.disabled);
  if (select && chosen) els[chosen.index].click();
  return {
    query: wantedName,
    optionCount: opts.length,
    matches: m,
    selected: !!(select && chosen),
    reason: select && !chosen ? "exact enabled option not found" : null,
  };
})("{name}", true);

One-to-One fill-and-verify evaluator:

((expected = {}) => {
  const c = (s) => (s || "").replace(/\s+/g, " ").trim(),
    v = (e) => !!e && (e.offsetWidth || e.offsetHeight || e.getClientRects().length),
    r = document.querySelector('div[role="dialog"]') || document,
    els = [
      ...r.querySelectorAll('input,textarea,[role="combobox"],.MuiSelect-select'),
    ].filter(v),
    ctx = (e) =>
      c(
        e.closest(".MuiFormControl-root,.MuiFormControl,fieldset,form,div")
          ?.innerText || "",
      ),
    allText = (e) =>
      [
        e.getAttribute("aria-label"),
        e.getAttribute("name"),
        e.getAttribute("placeholder"),
        ctx(e),
      ]
        .filter(Boolean)
        .join(" "),
    set = (e, x) => {
      if (!e) return;
      const p =
          e.tagName === "TEXTAREA"
            ? HTMLTextAreaElement.prototype
            : HTMLInputElement.prototype,
        s = Object.getOwnPropertyDescriptor(p, "value")?.set;
      s ? s.call(e, x) : (e.value = x);
      e.dispatchEvent(new Event("input", { bubbles: true }));
      e.dispatchEvent(new Event("change", { bubbles: true }));
    },
    find = (re) => els.find((e) => re.test(allText(e))),
    val = (e) => c(e?.value ?? e?.innerText ?? e?.textContent),
    member =
      find(/member|recipient|會員|成員|對象|對方/i) ||
      els.find((e) => e.matches('.MuiSelect-select,[role="combobox"]')),
    inviter = find(/inviter|invite|邀請人|邀請者/i),
    location = find(/location|地點|場所/i),
    date =
      find(/date|日期|時間/i) ||
      els.find((e) => e.tagName === "INPUT" && /20\d\d/.test(e.value)),
    topic =
      find(/topic|主題|內容|交流/i) || [...r.querySelectorAll("textarea")].find(v),
    locFallback = [...r.querySelectorAll("input.MuiOutlinedInput-input")].find(
      (e) => !location && !/20\d\d/.test(e.value),
    );
  if (expected.location != null) set(location || locFallback, expected.location);
  if (expected.date != null) set(date, expected.date);
  if (expected.topic != null) set(topic, expected.topic);
  const actual = {
      member: val(member) || null,
      inviter: val(inviter) || null,
      location: val(location || locFallback) || null,
      date: val(date) || null,
      topic: val(topic) || null,
    },
    matches = Object.fromEntries(
      Object.entries(expected)
        .filter(([, x]) => x != null)
        .map(([k, x]) => [k, actual[k] === c(x)]),
    );
  return {
    expected,
    actual,
    matches,
    allMatch: Object.values(matches).every(Boolean),
    fields: {
      member: !!member,
      inviter: !!inviter,
      location: !!(location || locFallback),
      date: !!date,
      topic: !!topic,
    },
    formText: c(r.innerText || r.textContent).slice(0, 3000),
  };
})({
  member: "{member-name}",
  inviter: "{inviter-name}",
  location: "{location}",
  date: "{date}",
  topic: "{topic}",
});

Submission-result extractor:

(() => {
  const c = (s) => (s || "").replace(/\s+/g, " ").trim(),
    v = (e) => !!e && (e.offsetWidth || e.offsetHeight || e.getClientRects().length),
    t = c(document.body?.innerText || document.body?.textContent),
    d = !!document.querySelector('div[role="dialog"]'),
    success =
      /success|submitted|saved|completed|成功|已提交|提交成功|儲存成功|保存成功/i.test(
        t,
      ),
    controls = [...document.querySelectorAll('button,a,[role="button"]')]
      .filter(v)
      .map((e) => ({
        label: c(e.innerText || e.textContent || e.getAttribute("aria-label")),
        href: e.getAttribute("href"),
        disabled: !!e.disabled,
      }));
  return {
    url: location.href,
    dialogPresent: d,
    modalGone: !d,
    success,
    visibleText: t.slice(0, 4000),
    controls,
  };
})();

Possible Friction Points

  • One-to-One and TYFCB forms have no stable direct URL in the observed installation; use authenticated dashboard controls.
  • The dashboard contains multiple 提交 controls. Qualify One-to-One by nearby One-to-One text and exclude internal-referral and chapter-education contexts.
  • Material UI options render only after bubbling mousedown plus click, followed by a wait for ul[role="listbox"] > li[role="option"].
  • Match options by exact normalized visible text and reject disabled options; ordinals vary by session.
  • Member and inviter lists may refresh independently; reopen the inviter list after member selection and wait for its current options.
  • One-to-One location and date are commonly input.MuiOutlinedInput-input; identify them by labels first. A year-like value is only a date fallback, and a non-year-like value is only a location fallback.
  • React-controlled fields require the native prototype value setter plus bubbling input and change events.
  • Do not use positional selectors for the final action. Qualify the enabled action within the verified form and verify visible success evidence afterward.
  • Authentication may append a transient ;jsessionid=...; preserve the session but never hard-code or expose it. Keep credentials, cookies, and session URLs runtime-only.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=bniconnectglobal.com&task=submit-tyfcb-slip