agents, gui: every agent's model list has Order, not only Codex's (cjyrainbow, #1052)
The model list under an agent (Pick on its row) had a third view, Order, for Codex alone (#855): the API said only Codex's list was orderable and refused an order for any other. The order itself was never Codex's only: provider.CatalogFor puts every agent's catalog in its order (InOrder), and the gateway's /models, the files magpie writes for each agent and Claude Code's modelPicker are all built from it. So the view is now on every agent's list, the API takes any agent's order, and one agent's order leaves the others' alone. Codex's note still says it is the order Codex lists them in; another agent's says it is the order magpie hands them to it, since an agent that sorts its own menu may sort them again. zh, ja, de. Verified: TestAgentModelOrderAPI fails on the old code (opencode: 400, the order refused); codex-model-order.test.cjs's new Claude Code case (en, zh, 440px wide: three views uncut and inside the box, Order lists the shown, Alt+Down posts the order, Default order posts none, nothing scrolls) fails on the old app.js (no Order). TestModelOrderReachesEveryAgent checks Claude Code's picker and opencode's written models follow their own order. Chromium and WebKit: codex-model-order, agent-models, agent-models-all- hidden, agent-models-loading, agent-model-groups, agent-models-picker, gui-ja, gui-de, i18n-keys pass. click-scroll's "Live and a day" and "room kept for a click" (Requests page) fail the same on origin/main, untouched here. go vet, builds (darwin, GOOS=linux nogui, GOOS=windows) and go test -tags nogui -count=1 ./... under a temp HOME pass.
This commit is contained in:
1 parent
35485f2d93
commit
76d82528e6
6 files changed
+154
-55
No files matched your search
@@ -0,0 +1,51 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/yetone/magpie/internal/provider"
|
||||
)
|
||||
|
||||
// An order dragged on the Agents page reaches every agent, not only Codex
|
||||
// (#1052): Claude Code's /model picker (viaMagpie, which writePicker turns
|
||||
// into settings.json's modelPicker) and the model files magpie writes for
|
||||
// the others (magpieModels) list the models in it, and one agent's order
|
||||
// leaves another's alone.
|
||||
func TestModelOrderReachesEveryAgent(t *testing.T) {
|
||||
syncHome(t)
|
||||
if err := provider.Save(provider.Provider{ID: "other", Name: "Other", Key: "k", Chat: "http://127.0.0.1:1/v1", Models: []string{"m1", "m2"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
refs := func(agent string) (out []string) {
|
||||
for _, o := range viaMagpie(agent, "") {
|
||||
out = append(out, o.Ref)
|
||||
}
|
||||
return out
|
||||
}
|
||||
ids := func(agent string) (out []string) {
|
||||
for _, m := range magpieModels(agent) {
|
||||
out = append(out, m.ID)
|
||||
}
|
||||
return out
|
||||
}
|
||||
was := ids("opencode")
|
||||
if want := []string{"relay/glm-4.6", "other/m1", "other/m2"}; !slices.Equal(was, want) {
|
||||
t.Fatalf("default %v, want %v", was, want)
|
||||
}
|
||||
if err := provider.SetModelOrder("claude", []string{"other/m2", "relay/glm-4.6"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := refs("claude"), []string{"other/m2", "relay/glm-4.6", "other/m1"}; !slices.Equal(got, want) {
|
||||
t.Errorf("Claude Code's picker %v, want %v", got, want)
|
||||
}
|
||||
if got := ids("opencode"); !slices.Equal(got, was) {
|
||||
t.Errorf("claude's order moved opencode's: %v", got)
|
||||
}
|
||||
if err := provider.SetModelOrder("opencode", []string{"other/m1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := ids("opencode"), []string{"other/m1", "relay/glm-4.6", "other/m2"}; !slices.Equal(got, want) {
|
||||
t.Errorf("opencode's models %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
// Codex's model list goes in the order dragged on the Agents page (#855):
|
||||
// the list says it can be ordered, an order comes back in it and stays in
|
||||
// the settings, the hidden kept as they were; none puts magpie's back, and
|
||||
// another agent's list can't be ordered.
|
||||
// the settings, the hidden kept as they were; none puts magpie's back.
|
||||
// Another agent's list takes an order of its own (#1052).
|
||||
func TestAgentModelOrderAPI(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
@@ -34,9 +34,8 @@ func TestAgentModelOrderAPI(t *testing.T) {
|
||||
mux := http.NewServeMux()
|
||||
agentModelsAPI(mux)
|
||||
type reply struct {
|
||||
Models []agentModelJSON
|
||||
Orderable bool
|
||||
Ordered bool
|
||||
Models []agentModelJSON
|
||||
Ordered bool
|
||||
}
|
||||
call := func(method, agent, body string) (int, reply) {
|
||||
t.Helper()
|
||||
@@ -55,8 +54,8 @@ func TestAgentModelOrderAPI(t *testing.T) {
|
||||
return out
|
||||
}
|
||||
_, got := call("GET", "codex", "")
|
||||
if !got.Orderable || got.Ordered {
|
||||
t.Fatalf("codex: orderable %v ordered %v", got.Orderable, got.Ordered)
|
||||
if got.Ordered {
|
||||
t.Fatalf("codex: ordered %v", got.Ordered)
|
||||
}
|
||||
if want := []string{"relay/m1", "relay/m2", "relay/m3"}; !slices.Equal(ids(got.Models), want) {
|
||||
t.Fatalf("%v, want %v", ids(got.Models), want)
|
||||
@@ -96,14 +95,18 @@ func TestAgentModelOrderAPI(t *testing.T) {
|
||||
if len(provider.ModelOrder("codex")) != 0 {
|
||||
t.Errorf("still saved %v", provider.ModelOrder("codex"))
|
||||
}
|
||||
// only Codex's
|
||||
if _, got = call("GET", "opencode", ""); got.Orderable {
|
||||
t.Error("opencode's list says it can be ordered")
|
||||
// every agent's list takes an order of its own (#1052), and Codex's
|
||||
// stays as it was
|
||||
if code, got = call("POST", "opencode", `{"order":["relay/m3"]}`); code != 200 || !got.Ordered {
|
||||
t.Fatalf("opencode: %d %+v", code, got)
|
||||
}
|
||||
if code, _ = call("POST", "opencode", `{"order":["relay/m3"]}`); code == 200 {
|
||||
t.Error("opencode's list took an order")
|
||||
if want := []string{"relay/m3", "relay/m1", "relay/m2", "relay/m4"}; !slices.Equal(ids(got.Models), want) {
|
||||
t.Errorf("opencode ordered %v, want %v", ids(got.Models), want)
|
||||
}
|
||||
if len(provider.ModelOrder("opencode")) != 0 {
|
||||
t.Errorf("opencode's order saved: %v", provider.ModelOrder("opencode"))
|
||||
if _, got = call("GET", "opencode", ""); !got.Ordered || ids(got.Models)[0] != "relay/m3" {
|
||||
t.Errorf("opencode read back %+v", got)
|
||||
}
|
||||
if len(provider.ModelOrder("codex")) != 0 {
|
||||
t.Errorf("opencode's order went to codex: %v", provider.ModelOrder("codex"))
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package gui
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -177,11 +176,6 @@ func agentModelList(a *agent.Agent) []agentModelJSON {
|
||||
return out
|
||||
}
|
||||
|
||||
// orderable reports whether the agent's model list is put in the order the
|
||||
// user drags it into: Codex's, whose /model lists the models by the
|
||||
// priority magpie gives them (#855).
|
||||
func orderable(id string) bool { return id == "codex" }
|
||||
|
||||
func agentModelsAPI(mux *http.ServeMux) {
|
||||
// a model the agent picks sent in its vendor's fast mode, or not (#954):
|
||||
// for is the option's fastFor, the agent whose requests it goes on
|
||||
@@ -206,13 +200,15 @@ func agentModelsAPI(mux *http.ServeMux) {
|
||||
fail(rw, err)
|
||||
return
|
||||
}
|
||||
writeJSON(rw, map[string]any{"models": agentModelList(a), "orderable": orderable(a.ListsFor()), "ordered": len(provider.ModelOrder(a.ListsFor())) > 0})
|
||||
writeJSON(rw, map[string]any{"models": agentModelList(a), "ordered": len(provider.ModelOrder(a.ListsFor())) > 0})
|
||||
})
|
||||
// hidden is every entry to take out of the agent's lists; the others
|
||||
// are shown, and one the agent is set to is kept in whatever is asked
|
||||
//
|
||||
// order, instead, is the order the agent's list puts them in (#855):
|
||||
// the ones named first, as named; none puts back magpie's own
|
||||
// the ones named first, as named; none puts back magpie's own. Every
|
||||
// agent's (#1052): its list, the gateway's /models and the files magpie
|
||||
// writes for it all come from provider.CatalogFor, which keeps it
|
||||
mux.HandleFunc("POST /api/agent-models/{id}", func(rw http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
Hidden []string
|
||||
@@ -228,10 +224,6 @@ func agentModelsAPI(mux *http.ServeMux) {
|
||||
return
|
||||
}
|
||||
if in.Order != nil {
|
||||
if !orderable(a.ListsFor()) {
|
||||
fail(rw, errors.New(a.Name+"'s model list can't be put in an order"))
|
||||
return
|
||||
}
|
||||
if err := provider.SetModelOrder(a.ListsFor(), *in.Order); err != nil {
|
||||
fail(rw, err)
|
||||
return
|
||||
|
||||
@@ -2242,9 +2242,10 @@ async function openAgentModels(a, anchor, ev) {
|
||||
},
|
||||
};
|
||||
document.addEventListener("mousedown", loading.away, true);
|
||||
// an agent whose list goes in the order dragged here (Codex's, #855)
|
||||
let models, orderable = false, ordered = false;
|
||||
try { ({ models, orderable, ordered } = await api("agent-models/" + encodeURIComponent(a.id))); }
|
||||
// every agent's list goes in the order dragged here (Codex's, #855; the
|
||||
// others', #1052)
|
||||
let models, ordered = false;
|
||||
try { ({ models, ordered } = await api("agent-models/" + encodeURIComponent(a.id))); }
|
||||
catch (e) {
|
||||
if (agentModelsLoading === loading) { loading.drop(); status(e.message, "err"); }
|
||||
return;
|
||||
@@ -2270,8 +2271,7 @@ async function openAgentModels(a, anchor, ev) {
|
||||
const seg = el("div", "am-seg");
|
||||
const segAll = el("button", "on", t("All")), segOn = el("button", "", t("Shown")), segOrder = el("button", "", t("Order"));
|
||||
segAll.type = segOn.type = segOrder.type = "button";
|
||||
seg.append(segAll, segOn);
|
||||
if (orderable) seg.append(segOrder);
|
||||
seg.append(segAll, segOn, segOrder);
|
||||
tools.append(search, seg);
|
||||
const list = el("div", "am-list");
|
||||
const foot = el("div", "am-foot");
|
||||
@@ -2517,7 +2517,12 @@ async function openAgentModels(a, anchor, ev) {
|
||||
// ordering is of the whole list: no search, no provider picked, and the
|
||||
// foot puts magpie's own order back rather than showing or hiding
|
||||
box.classList.toggle("ordering", order);
|
||||
footNote.textContent = order ? t("Drag to put them in the order {agent} lists them; new models go last", { agent: a.name }) : t("New models are shown");
|
||||
// Codex's /model, in WSL too, lists them as handed (#855); another
|
||||
// agent is handed them in it, and one that sorts its own menu may sort
|
||||
// them again
|
||||
footNote.textContent = !order ? t("New models are shown")
|
||||
: a.id.split("@wsl:")[0] === "codex" ? t("Drag to put them in the order {agent} lists them; new models go last", { agent: a.name })
|
||||
: t("Drag to put them in the order magpie hands them to {agent}; new models go last", { agent: a.name });
|
||||
if (order) foot.replaceChildren(footNote, el("span", "sp"), unorder);
|
||||
else foot.replaceChildren(footNote, el("span", "sp"), hideAll, el("span", "am-dot", "·"), reset);
|
||||
list.scrollTop = 0;
|
||||
|
||||
@@ -202,6 +202,7 @@ const I18N = {
|
||||
"Default order": "恢复默认顺序",
|
||||
"Drag to move · Alt+↑/↓ from the keyboard": "拖动调整位置 · 键盘用 Alt+↑/↓",
|
||||
"Drag to put them in the order {agent} lists them; new models go last": "拖动排列 {agent} 列表中的顺序;新模型排在最后",
|
||||
"Drag to put them in the order magpie hands them to {agent}; new models go last": "拖动排列 magpie 交给 {agent} 的模型顺序;新模型排在最后",
|
||||
"{n} hidden": "已隐藏 {n} 个",
|
||||
"{n} hidden agents": "已隐藏的 Agent({n} 个)",
|
||||
"Show {n} more ({h} hidden)": "显示其余 {n} 个(含 {h} 个已隐藏)",
|
||||
@@ -3765,6 +3766,7 @@ const I18N = {
|
||||
"Default order": "既定の順に戻す",
|
||||
"Drag to move · Alt+↑/↓ from the keyboard": "ドラッグで移動 · キーボードは Alt+↑/↓",
|
||||
"Drag to put them in the order {agent} lists them; new models go last": "ドラッグで {agent} の一覧の並び順を変更。新しいモデルは末尾に追加されます",
|
||||
"Drag to put them in the order magpie hands them to {agent}; new models go last": "ドラッグで magpie が {agent} に渡すモデルの並び順を変更。新しいモデルは末尾に追加されます",
|
||||
"{n} hidden": "{n} 件非表示",
|
||||
"{n} hidden agents": "非表示のエージェント({n})",
|
||||
"Show {n} more ({h} hidden)": "さらに {n} 件表示(うち {h} 件は非表示)",
|
||||
@@ -7327,6 +7329,7 @@ const I18N = {
|
||||
"Default order": "Standardreihenfolge",
|
||||
"Drag to move · Alt+↑/↓ from the keyboard": "Ziehen zum Verschieben · Alt+↑/↓ per Tastatur",
|
||||
"Drag to put them in the order {agent} lists them; new models go last": "Ziehen, um die Reihenfolge in {agent} festzulegen; neue Modelle kommen ans Ende",
|
||||
"Drag to put them in the order magpie hands them to {agent}; new models go last": "Ziehen, um die Reihenfolge festzulegen, in der magpie {agent} die Modelle übergibt; neue Modelle kommen ans Ende",
|
||||
"{n} hidden": "{n} ausgeblendet",
|
||||
"{n} hidden agents": "{n} ausgeblendete Agenten",
|
||||
"Show {n} more ({h} hidden)": "{n} weitere anzeigen ({h} ausgeblendet)",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// "Order" lists the models shown as Codex does, the account's own first,
|
||||
// and a drag or Alt+↑/↓ puts one elsewhere, saved at once; "Default order"
|
||||
// puts magpie's back. The hidden stay out, and nothing scrolls the page.
|
||||
// Every agent's list has the Order (#1052): Claude Code's, which had none,
|
||||
// orders and saves the same way, saying it is the order magpie hands it.
|
||||
const assert = require("node:assert/strict");
|
||||
const fs = require("node:fs/promises");
|
||||
const path = require("node:path");
|
||||
@@ -19,13 +21,15 @@ const models = () => [
|
||||
{ id: "codex-acc/gpt-5-codex", name: "gpt-5-codex", group: "ChatGPT", icon: "openai", own: true },
|
||||
];
|
||||
|
||||
function fixture(lang, orderable) {
|
||||
const first = models();
|
||||
let list = models();
|
||||
function fixture(lang, agent = { id: "codex", name: "Codex", icon: "codex-color" }) {
|
||||
// only Codex has models of its own, which the server marks
|
||||
const mine = () => models().map((m) => agent.id === "codex" ? m : { ...m, own: undefined });
|
||||
const first = mine();
|
||||
let list = mine();
|
||||
const posts = [];
|
||||
const state = () => ({
|
||||
agents: [{
|
||||
id: "codex", name: "Codex", path: "/test/config.toml", icon: "codex-color", wired: true,
|
||||
id: agent.id, name: agent.name, path: "/test/config.toml", icon: agent.icon, wired: true,
|
||||
fields: [{ key: "model", label: "model", value: "gpt-5.5", options: [{ value: "gpt-5.5", label: "gpt-5.5", ref: "codex-acc/gpt-5.5" }] }],
|
||||
models: { shown: 4, listed: 5, by: [{ name: "Relay", icon: "anthropic", n: 2 }, { name: "ChatGPT", icon: "openai", n: 2 }] },
|
||||
}],
|
||||
@@ -38,7 +42,7 @@ function fixture(lang, orderable) {
|
||||
if (url.pathname === "/boot.js") return route.fulfill({ contentType: "text/javascript", body: `window.bootPrefs = {lang:"${lang}",theme:"light",web:true};` });
|
||||
if (url.pathname === "/wails/runtime.js") return route.fulfill({ contentType: "text/javascript", body: "export const Window = {};" });
|
||||
if (url.pathname === "/api/state") return json(state());
|
||||
if (url.pathname === "/api/agent-models/codex") {
|
||||
if (url.pathname === "/api/agent-models/" + agent.id) {
|
||||
if (req.method() === "POST") {
|
||||
const { order } = req.postDataJSON();
|
||||
posts.push(order);
|
||||
@@ -46,7 +50,7 @@ function fixture(lang, orderable) {
|
||||
list = order.length ? [...first].sort((x, y) => at(x) - at(y)) : first;
|
||||
return json({ models: list, ordered: order.length > 0 });
|
||||
}
|
||||
return json({ models: list, orderable, ordered: false });
|
||||
return json({ models: list, ordered: false });
|
||||
}
|
||||
if (url.pathname === "/api/groups") return json({ groups: [] });
|
||||
if (url.pathname === "/api/providers") return json({ providers: [], gateway: { running: true, window: true } });
|
||||
@@ -59,8 +63,8 @@ function fixture(lang, orderable) {
|
||||
}
|
||||
|
||||
const W = {
|
||||
en: { entry: "1 hidden", order: "Order", unorder: "Default order", note: "Drag to put them in the order Codex lists them; new models go last" },
|
||||
zh: { entry: "已隐藏 1 个", order: "排序", unorder: "恢复默认顺序", note: "拖动排列 Codex 列表中的顺序;新模型排在最后" },
|
||||
en: { entry: "1 hidden", order: "Order", unorder: "Default order", note: "Drag to put them in the order Codex lists them; new models go last", handed: "Drag to put them in the order magpie hands them to Claude Code; new models go last" },
|
||||
zh: { entry: "已隐藏 1 个", order: "排序", unorder: "恢复默认顺序", note: "拖动排列 Codex 列表中的顺序;新模型排在最后", handed: "拖动排列 magpie 交给 Claude Code 的模型顺序;新模型排在最后" },
|
||||
};
|
||||
|
||||
for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) {
|
||||
@@ -72,7 +76,7 @@ for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium",
|
||||
page.setDefaultTimeout(5000);
|
||||
const errors = [];
|
||||
page.on("pageerror", (e) => errors.push(e.message));
|
||||
const fx = fixture(lang, true);
|
||||
const fx = fixture(lang);
|
||||
await page.route("**/*", fx.serve);
|
||||
t.after(async () => {
|
||||
if (process.env.ARTIFACT_DIR) {
|
||||
@@ -148,18 +152,59 @@ for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium",
|
||||
assert.deepEqual(errors, []);
|
||||
});
|
||||
}
|
||||
test(`${engine}: a list that can't be ordered has no Order`, async (t) => {
|
||||
const browser = await (engine === "webkit" ? webkit.launch() : chromium.launch({ channel: "chromium" }));
|
||||
const page = await browser.newPage({ viewport: { width: 1000, height: 700 } });
|
||||
page.setDefaultTimeout(5000);
|
||||
t.after(() => browser.close());
|
||||
await page.route("**/*", fixture("en", false).serve);
|
||||
await page.goto("http://magpie.test/");
|
||||
const row = page.locator('.row.agent[data-id="codex"]');
|
||||
await row.locator(".ag-link").click();
|
||||
await row.locator(".ag-exp .ag-chips .ag-quiet").click();
|
||||
const pop = page.locator(".am-pop:not(.leaving)");
|
||||
await pop.waitFor();
|
||||
assert.equal(await pop.locator(".am-seg button").count(), 2);
|
||||
});
|
||||
for (const lang of ["en", "zh"]) {
|
||||
test(`${engine} ${lang}: Claude Code's list has an Order too (#1052)`, async (t) => {
|
||||
const w = W[lang];
|
||||
const browser = await (engine === "webkit" ? webkit.launch() : chromium.launch({ channel: "chromium" }));
|
||||
// narrow: the three views still fit beside the search
|
||||
const page = await browser.newPage({ viewport: { width: 440, height: 700 } });
|
||||
page.setDefaultTimeout(5000);
|
||||
const errors = [];
|
||||
page.on("pageerror", (e) => errors.push(e.message));
|
||||
const fx = fixture(lang, { id: "claude", name: "Claude Code", icon: "claude-color" });
|
||||
await page.route("**/*", fx.serve);
|
||||
t.after(async () => {
|
||||
if (process.env.ARTIFACT_DIR) {
|
||||
await fs.mkdir(process.env.ARTIFACT_DIR, { recursive: true });
|
||||
await page.screenshot({ path: path.join(process.env.ARTIFACT_DIR, `${engine}-${lang}-claude-order.png`) });
|
||||
}
|
||||
await browser.close();
|
||||
});
|
||||
await page.goto("http://magpie.test/");
|
||||
const row = page.locator('.row.agent[data-id="claude"]');
|
||||
await row.locator(".ag-link").click();
|
||||
const entry = row.locator(".ag-exp .ag-chips .ag-quiet");
|
||||
await entry.waitFor();
|
||||
await page.waitForFunction(() => document.getAnimations().length === 0);
|
||||
const scroll = () => page.evaluate(() => [scrollY, document.scrollingElement.scrollTop, $("#view-agents").scrollTop]);
|
||||
const was = await scroll();
|
||||
await entry.click();
|
||||
const pop = page.locator(".am-pop:not(.leaving)");
|
||||
await pop.waitFor();
|
||||
assert.equal(await pop.locator(".am-seg button").count(), 3);
|
||||
const seg = pop.locator(".am-seg button", { hasText: w.order });
|
||||
// the three views whole, not squeezed to "…", and inside the box
|
||||
for (const b of await pop.locator(".am-seg button").all()) {
|
||||
assert(await b.evaluate((e) => e.scrollWidth <= e.clientWidth + 1), "a view's name is cut");
|
||||
}
|
||||
const box = await pop.boundingBox(), sb = await seg.boundingBox();
|
||||
assert(sb.x + sb.width <= box.x + box.width + 1 && sb.x >= box.x - 1, "Order is outside the box");
|
||||
await seg.click();
|
||||
assert.deepEqual(await scroll(), was, "nothing scrolls");
|
||||
const names = async () => (await pop.locator(".am-or .n").allInnerTexts()).map((s) => s.trim());
|
||||
assert.deepEqual(await names(), ["claude-opus-5.5", "claude-sonnet-4-5", "gpt-5.5", "gpt-5-codex"], "the shown as handed, the hidden left out");
|
||||
assert.equal(await pop.locator(".am-foot").innerText().then((s) => s.includes(w.handed)), true);
|
||||
await pop.locator(".am-or").first().focus();
|
||||
await page.keyboard.press("Alt+ArrowDown");
|
||||
await page.waitForTimeout(150);
|
||||
assert.deepEqual(fx.posts.at(-1), ["relay/claude-sonnet-4-5", "relay/claude-opus-5.5", "codex-acc/gpt-5.5", "codex-acc/gpt-5-codex"]);
|
||||
assert.deepEqual(await names(), ["claude-sonnet-4-5", "claude-opus-5.5", "gpt-5.5", "gpt-5-codex"]);
|
||||
const unorder = pop.locator(".am-foot .am-reset", { hasText: w.unorder });
|
||||
await unorder.click();
|
||||
await page.waitForTimeout(150);
|
||||
assert.deepEqual(fx.posts.at(-1), []);
|
||||
assert.deepEqual(await scroll(), was, "nothing scrolls");
|
||||
assert.deepEqual(errors, []);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user