import {test, expect} from '@playwright/test'; import { apiAddCollaborator, apiCreateFiles, apiCreatePR, apiCreateRepo, apiCreateReview, apiCreateUser, apiUserHeaders, loginUser, randomString, } from './utils.ts'; test('pr review flow', async ({page, request}) => { const poster = `rv-poster-${randomString(8)}`; const reviewer = `rv-reviewer-${randomString(8)}`; const officialReviewer = `rv-official-${randomString(8)}`; await Promise.all([ apiCreateUser(request, poster), apiCreateUser(request, reviewer), apiCreateUser(request, officialReviewer), ]); const posterHeaders = apiUserHeaders(poster); const repoName = `e2e-prreview-${randomString(8)}`; await apiCreateRepo(request, {name: repoName, headers: posterHeaders}); await Promise.all([ apiAddCollaborator( request, poster, repoName, officialReviewer, {headers: posterHeaders}, ), apiCreateFiles( request, poster, repoName, [{path: 'added.txt', content: 'new content\n'}], {branch: 'main', newBranch: 'feat', headers: posterHeaders}, ), ]); const prIndex = await apiCreatePR(request, poster, repoName, 'feat', 'main', 'review test', {headers: posterHeaders}); const pullUrl = `/${poster}/${repoName}/pulls/${prIndex}`; const reRequestAndApprove = async ( username: string, approvalTooltip: string, pendingStateClass: string, official: boolean, ) => { await page.goto(pullUrl); const reviewerItem = page.locator('.issue-sidebar-combo .ui.relaxed.list > .item', { has: page.locator(`a[href="/${username}"]`), }); const reviewState = reviewerItem.locator('span[data-tooltip-content]'); await expect(reviewState).toHaveAttribute('data-tooltip-content', approvalTooltip); await reviewerItem.locator('a[data-tooltip-content="Re-request review"]').click(); await expect(reviewState).toHaveAttribute('data-tooltip-content', 'Review pending'); await expect(reviewState.locator(`.octicon-dot-fill.${pendingStateClass}`)).toBeVisible(); expect(await apiCreateReview(request, poster, repoName, prIndex, { event: 'APPROVED', headers: apiUserHeaders(username), })).toMatchObject({state: 'APPROVED', official}); }; // reviewer seeds an inline comment via API so the poster's UI reply exercises the reply-to-review path (#35994) await Promise.all([ apiCreateReview(request, poster, repoName, prIndex, { comments: [{path: 'added.txt', body: 'inline to reply to', new_position: 1}], headers: apiUserHeaders(reviewer), }), apiCreateReview(request, poster, repoName, prIndex, { event: 'APPROVED', headers: apiUserHeaders(officialReviewer), }), loginUser(page, poster), ]); await page.goto(`${pullUrl}/files`); // diff viewer renders the added file with its header and one added-line row const fileBox = page.locator('.diff-file-box[data-new-filename="added.txt"]'); await expect(fileBox.locator('.diff-file-header .file-link')).toHaveText('added.txt'); await expect(fileBox.locator('tr.add-code')).toHaveCount(1); // commits tab badge reflects the single PR commit, and the diff stats header counts one changed file const commitsTab = page.locator('.ui.pull.tabular.menu a.item', {has: page.locator('.octicon-git-commit')}); await expect(commitsTab.locator('.label')).toHaveText('1'); await expect(page.locator('.diff-detail-stats')).toContainText(/1 changed file/); // poster replies to the reviewer's inline comment const conversation = fileBox.locator('.conversation-holder'); await conversation.locator('.comment-form-reply').click(); const replyForm = conversation.locator('form'); await replyForm.locator('textarea[name="content"]').fill('my reply body'); await replyForm.getByRole('button', {name: 'Reply', exact: true}).click(); await expect(conversation.locator('.comment-body')).toContainText(['inline to reply to', 'my reply body']); await page.context().clearCookies(); await loginUser(page, reviewer); await page.goto(`${pullUrl}/files`); await page.locator('#review-box .js-btn-review').click(); const panel = page.locator('.review-box-panel'); await panel.locator('textarea[name="content"]').fill(`First approval from ${reviewer}`); await panel.getByRole('button', {name: 'Approve', exact: true}).click(); await expect(page.locator('.timeline-item').filter({hasText: `First approval from ${reviewer}`})).toBeVisible(); await reRequestAndApprove(reviewer, 'Uncounted approval', 'tw-text-text-light', false); await page.context().clearCookies(); await loginUser(page, officialReviewer); await reRequestAndApprove(officialReviewer, 'Approved', 'tw-text-yellow', true); });