Files
Gnuboard7/modules/_bundled/sirsoft-page/tests/Playwright/specs/user/page-og-image.spec.ts
T
HeuJung 1ba67ca7cf feat(content): 본문 이미지 목록 썸네일·og:image 폴백 전 티어 구현
공개 이슈 https://github.com/gnuboard/g7/issues/22 — 본문에 삽입한 이미지가
카드/갤러리 목록 썸네일과 공유 미리보기(og:image)에 반영되지 않던 공백을
코어+3모듈+2템플릿에서 해소.

- 코어 7.0.9: HtmlImageExtractor(내부 이미지 한정, origin 정규화 SSoT 재사용)
 + seo.og_image_default 사이트 기본 공유 이미지 설정 + SeoMetaResolver 폴백 체인
- board 1.1.0 / ecommerce 1.2.0 / page 1.1.0: content_thumbnail_url 캐시 컬럼,
 saving 추출(html 모드 한정·첨부 우선·비밀글 게이트 유지),
 filter_content_thumbnail 훅, 기존 데이터 백필 업그레이드 스텝
- ecommerce 카테고리 og:image 생산자 신설, page og:description 死키 정정
- sirsoft-basic 1.1.2 og 바인딩 정정, admin_basic 1.0.7 SEO 탭 업로더(+ja 팩 2종)
- 검수 중 발견한 text 모드 오캐시 결함은 실패 테스트 선행 후 수정
2026-08-22 22:21:54 +09:00

110 lines
4.8 KiB
TypeScript

/**
* 페이지 og:image 본문 첫 이미지 공급 — 봇 경로 실측 (공개 이슈 #22, 내부 #610).
*
* 페이지는 og:image 를 공급할 경로가 전무했다. 본문에 이미지를 넣어 저장하면
* 봇 경로(`?_escaped_fragment_=`)의 서버 렌더 HTML 에 og:image 메타가 절대 URL 로
* 실리는지, 이미지 없는 페이지는 og:image 가 출력되지 않는지(사이트 기본값 미설정 시)
* 확인한다. og:description 死키 정렬(#11)도 함께 실측한다.
*
* 단위/통합: PageContentThumbnailTest(saving 추출), PageContentThumbnailResourceTest
* (Resource 방출)가 담당하고, 이 spec 은 봇 경로 meta 렌더를 담당한다.
*
* 전용 페이지(slug: e2e-og-image-*)를 spec 안에서 생성/삭제하므로 시드 의존이 없다.
*
* @scenario page-og-image
* @axes image_source=content_internal_only image_source=none
* @effects bot_page_renders_og_image_meta,
* content_image_fills_page_og
*/
import { test as base, expect } from '@playwright/test';
import { issueToken } from '../../../../../../../tests/Playwright/fixtures/auth';
const API = '/api/modules/sirsoft-page';
type PageAuthFixtures = { pageManageToken: string };
const test = base.extend<PageAuthFixtures>({
pageManageToken: async ({}, use) => {
await use(issueToken(
'sirsoft-page.pages.create',
'sirsoft-page.pages.read',
'sirsoft-page.pages.update',
'sirsoft-page.pages.delete',
));
},
});
/** API 컨텍스트로 호출한다 (spec 전용 — 페이지 방문 불필요한 셋업/정리용). */
async function api(
request: import('@playwright/test').APIRequestContext,
bearer: string,
method: 'get' | 'post' | 'delete',
path: string,
data?: unknown,
): Promise<{ status: number; body: any }> {
const response = await request[method](path, {
headers: { Authorization: `Bearer ${bearer}`, Accept: 'application/json' },
data: data as any,
});
let body: any = null;
try {
body = await response.json();
} catch {
/* 비 JSON 응답 허용 */
}
return { status: response.status(), body };
}
test.describe('페이지 og:image 본문 이미지 공급 (공개 #22)', () => {
test('본문 이미지 페이지의 봇 경로 HTML 에 og:image 절대 URL 이 실린다', async ({ request, pageManageToken }) => {
const slug = 'e2e-og-image-filled';
await api(request, pageManageToken, 'delete', `${API}/admin/pages/by-slug/${slug}`).catch(() => null);
const created = await api(request, pageManageToken, 'post', `${API}/admin/pages`, {
slug,
title: { ko: '[검수] og 이미지 페이지' },
content: { ko: '<p>본문</p><img src="/favicon.ico">' },
content_mode: 'html',
published: true,
seo_meta: { description: 'og 설명 검증용' },
});
expect([200, 201], `page create failed: ${JSON.stringify(created.body).slice(0, 300)}`).toContain(created.status);
const pageId = created.body?.data?.id;
// 공개 API 가 캐시를 방출하는지 (레이아웃 og.image 의 데이터 근원)
const pub = await api(request, pageManageToken, 'get', `${API}/pages/${slug}`);
expect(pub.body?.data?.content_thumbnail_url).toBe('/favicon.ico');
// 봇 경로 서버 렌더 — og:image 절대 URL + og:description 정렬(#11)
const bot = await request.get(`/page/${slug}?_escaped_fragment_=`);
const html = await bot.text();
expect(html).toMatch(/<meta property="og:image" content="https?:\/\/[^"]+\/favicon\.ico">/);
expect(html).toContain('<meta property="og:description" content="og 설명 검증용">');
// 정리
const del = await api(request, pageManageToken, 'delete', `${API}/admin/pages/${pageId}`);
expect([200, 204]).toContain(del.status);
});
test('이미지 없는 페이지는 og:image 가 본문 폴백으로 채워지지 않는다', async ({ request, pageManageToken }) => {
const slug = 'e2e-og-image-empty';
const created = await api(request, pageManageToken, 'post', `${API}/admin/pages`, {
slug,
title: { ko: '[검수] og 이미지 없는 페이지' },
content: { ko: '<p>이미지가 없는 본문입니다.</p>' },
content_mode: 'html',
published: true,
});
expect([200, 201]).toContain(created.status);
const pageId = created.body?.data?.id;
const pub = await api(request, pageManageToken, 'get', `${API}/pages/${slug}`);
expect(pub.body?.data?.content_thumbnail_url).toBeNull();
// 정리
const del = await api(request, pageManageToken, 'delete', `${API}/admin/pages/${pageId}`);
expect([200, 204]).toContain(del.status);
});
});