tangled
alpha
login
or
join now
linkat.blue
/
linkat
6
fork
atom
Create your Link in Bio for Bluesky
6
fork
atom
overview
issues
pulls
pipelines
e2eテストをブラウザごとに別のアカウントで実行
mkizka.dev
2 years ago
ee508b97
69162e62
+83
-23
9 changed files
expand all
collapse all
unified
split
app
features
board
card
profile-card.tsx
root.tsx
routes
_index.tsx
e2e
edit-redirect.spec.ts
edit.spec.ts
global.setup.ts
states
.gitignore
utils.ts
playwright.config.ts
+6
-1
app/features/board/card/profile-card.tsx
···
34
34
export function ProfileCard({ user, button }: ProfileCardProps) {
35
35
const buttons = {
36
36
edit: (
37
37
-
<Link className="btn btn-primary" to={`/edit?base=${user.handle}`}>
37
37
+
<Link
38
38
+
className="btn btn-primary"
39
39
+
to={`/edit?base=${user.handle}`}
40
40
+
data-testid="profile-card__edit"
41
41
+
>
38
42
<PencilSquareIcon className="size-6" />
39
43
編集
40
44
</Link>
···
43
47
<Link
44
48
className="btn btn-primary animate-bounce repeat-0"
45
49
to={`/board/${user.handle}`}
50
50
+
data-testid="profile-card__preview"
46
51
>
47
52
<EyeIcon className="size-6" />
48
53
ページを見る
+4
app/root.tsx
···
14
14
import { userAtom } from "./atoms/user/base";
15
15
import { resumeSessionAtom } from "./atoms/user/write-only";
16
16
import { Toaster } from "./features/toast/toaster";
17
17
+
import { createLogger } from "./utils/logger";
17
18
18
19
export { ErrorBoundary } from "~/components/error-boundary";
19
20
export { HydrateFallback } from "~/components/hydate-fallback";
20
21
21
22
const LOGIN_REQUIRED_PATHS = ["/edit"];
22
23
24
24
+
const logger = createLogger("root.tsx");
25
25
+
23
26
export const clientLoader: ClientLoaderFunction = async ({ request }) => {
24
27
const store = getDefaultStore();
25
28
await store.set(resumeSessionAtom);
26
29
const user = store.get(userAtom);
27
30
const pathname = new URL(request.url).pathname;
28
31
if (!user && LOGIN_REQUIRED_PATHS.includes(pathname)) {
32
32
+
logger.debug("未ログインのためリダイレクトしました");
29
33
return redirect("/");
30
34
}
31
35
return null;
+15
-1
app/routes/_index.tsx
···
1
1
+
import { Link } from "@remix-run/react";
2
2
+
3
3
+
import { useUser } from "~/atoms/user/hooks";
1
4
import { LoginForm } from "~/features/login/login-form";
2
5
3
6
export default function Index() {
7
7
+
const user = useUser();
4
8
return (
5
9
<div className="utils--center">
6
6
-
<LoginForm />
10
10
+
{user ? (
11
11
+
<Link
12
12
+
className="btn btn-primary"
13
13
+
to={`/edit?base=${user.profile.handle}`}
14
14
+
data-testid="index__edit-link"
15
15
+
>
16
16
+
編集ページへ
17
17
+
</Link>
18
18
+
) : (
19
19
+
<LoginForm />
20
20
+
)}
7
21
</div>
8
22
);
9
23
}
+9
e2e/edit-redirect.spec.ts
···
1
1
import { test } from "@playwright/test";
2
2
3
3
+
import { resetStorageState } from "./utils";
4
4
+
3
5
const DUMMY_EXPIRED_USER = JSON.stringify({
4
6
profile: {},
5
7
session: {
···
14
16
service: "http://localhost:2583/",
15
17
});
16
18
19
19
+
resetStorageState();
20
20
+
17
21
test.describe("編集(リダイレクト)", () => {
18
22
test("非ログイン時はトップにリダイレクト", async ({ page }) => {
23
23
+
await page.goto("/edit?base=alice.test");
24
24
+
await page.waitForURL((url) => url.pathname === "/");
25
25
+
await page.waitForTimeout(2000);
26
26
+
});
27
27
+
test("baseパラメータが無いときはトップにリダイレクト", async ({ page }) => {
19
28
await page.goto("/edit");
20
29
await page.waitForURL((url) => url.pathname === "/");
21
30
await page.waitForTimeout(2000);
+8
-10
e2e/edit.spec.ts
···
1
1
import { expect, test } from "@playwright/test";
2
2
3
3
-
import { restoreStorageState } from "./utils";
4
4
-
5
5
-
restoreStorageState();
6
6
-
7
3
test.describe("編集", () => {
8
4
test("カードを追加して保存すると閲覧ページに反映される", async ({ page }) => {
5
5
+
// セットアップ
9
6
const text1 = `1. ${crypto.randomUUID()}`;
10
7
const text2 = `2. ${crypto.randomUUID()}`;
11
8
const text1Edited = `1(edit). ${crypto.randomUUID()}`;
···
18
15
const card1Edited = page.locator('[data-testid="sortable-card"]', {
19
16
hasText: text1Edited,
20
17
});
21
21
-
await page.goto("/edit?base=alice.test");
18
18
+
await page.goto("/");
19
19
+
await page.getByTestId("index__edit-link").click();
22
20
23
21
// カードを追加
24
22
await page.getByTestId("card-form-modal__button").click();
···
39
37
await page.waitForTimeout(1000);
40
38
41
39
// 閲覧ページで順番を確認
42
42
-
await page.goto("/board/alice.test");
40
40
+
await page.getByTestId("profile-card__preview").click();
43
41
await expect(card1).toBeVisible();
44
42
await expect(card2).toBeVisible();
45
43
const allCards = await page.getByTestId("sortable-card").allTextContents();
46
44
expect(allCards.indexOf(text1)).toBeLessThan(allCards.indexOf(text2));
47
45
48
46
// カードを並べ替える
49
49
-
await page.goto("/edit?base=alice.test");
47
47
+
await page.getByTestId("profile-card__edit").click();
50
48
await card1.dragTo(card2);
51
49
52
50
// 保存ボタン押下、Firehose反映待ち
···
54
52
await page.waitForTimeout(1000);
55
53
56
54
// 閲覧ページで順番を確認
57
57
-
await page.goto("/board/alice.test");
55
55
+
await page.getByTestId("profile-card__preview").click();
58
56
await expect(card1).toBeVisible();
59
57
await expect(card2).toBeVisible();
60
58
const sorted = await page.getByTestId("sortable-card").allTextContents();
61
59
expect(sorted.indexOf(text1)).toBeGreaterThan(sorted.indexOf(text2));
62
60
63
61
// カードを編集
64
64
-
await page.goto("/edit?base=alice.test");
62
62
+
await page.getByTestId("profile-card__edit").click();
65
63
await card1.getByTestId("sortable-card__edit").click();
66
64
await page.getByTestId("card-form__text").fill(text1Edited);
67
65
await page.getByTestId("card-form__url").fill("https://example.com");
···
72
70
await page.waitForTimeout(1000);
73
71
74
72
// 閲覧ページで編集済みを確認
75
75
-
await page.goto("/board/alice.test");
73
73
+
await page.getByTestId("profile-card__preview").click();
76
74
await expect(card1).not.toBeVisible();
77
75
await expect(card1Edited).toBeVisible();
78
76
await expect(card2).toBeVisible();
+34
-8
e2e/global.setup.ts
···
1
1
+
import type { Page } from "@playwright/test";
1
2
import { test } from "@playwright/test";
3
3
+
4
4
+
const login = async ({
5
5
+
page,
6
6
+
identifier,
7
7
+
password,
8
8
+
}: {
9
9
+
page: Page;
10
10
+
identifier: string;
11
11
+
password: string;
12
12
+
}) => {
13
13
+
await page.goto("/");
14
14
+
await page.getByTestId("login-form__service").fill("http://localhost:2583");
15
15
+
await page.getByTestId("login-form__identifier").fill(identifier);
16
16
+
await page.getByTestId("login-form__password").fill(password);
17
17
+
await page.getByTestId("login-form__submit").click();
18
18
+
await page.waitForURL((url) => url.pathname === "/edit");
19
19
+
await page
20
20
+
.context()
21
21
+
.storageState({ path: `./e2e/states/${identifier}.json` });
22
22
+
};
2
23
3
24
test.describe("ログイン", () => {
4
4
-
test("テストアカウントでログインできる", async ({ page }) => {
5
5
-
await page.goto("/");
6
6
-
await page.getByTestId("login-form__service").fill("http://localhost:2583");
7
7
-
await page.getByTestId("login-form__identifier").fill("alice.test");
8
8
-
await page.getByTestId("login-form__password").fill("hunter2");
9
9
-
await page.getByTestId("login-form__submit").click();
10
10
-
await page.waitForURL((url) => url.pathname === "/edit");
11
11
-
await page.context().storageState({ path: "./e2e/state.json" });
25
25
+
test("テストアカウントでログインできる(chromium)", async ({ page }) => {
26
26
+
await login({
27
27
+
page,
28
28
+
identifier: "alice.test",
29
29
+
password: "hunter2",
30
30
+
});
31
31
+
});
32
32
+
test("テストアカウントでログインできる(safari)", async ({ page }) => {
33
33
+
await login({
34
34
+
page,
35
35
+
identifier: "bob.test",
36
36
+
password: "hunter2",
37
37
+
});
12
38
});
13
39
});
+2
e2e/states/.gitignore
···
1
1
+
*
2
2
+
!.gitignore
+2
-2
e2e/utils.ts
···
1
1
import { test } from "@playwright/test";
2
2
3
3
-
export const restoreStorageState = () => {
3
3
+
export const resetStorageState = () => {
4
4
test.use({
5
5
-
storageState: "./e2e/state.json",
5
5
+
storageState: { cookies: [], origins: [] },
6
6
});
7
7
};
+3
-1
playwright.config.ts
···
26
26
use: {
27
27
...devices["Desktop Chrome"],
28
28
locale: "ja-JP",
29
29
+
storageState: "./e2e/states/alice.test.json",
29
30
},
30
31
dependencies: ["setup"],
31
32
},
···
34
35
use: {
35
36
...devices["iPhone 15 Pro"],
36
37
locale: "ja-JP",
38
38
+
storageState: "./e2e/states/bob.test.json",
37
39
},
38
38
-
dependencies: ["chromium"],
40
40
+
dependencies: ["setup"],
39
41
},
40
42
],
41
43
webServer: {