diff --git a/CHANGELOG.md b/CHANGELOG.md index 1471f7e910..9c50d97820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This is the log of notable changes to EAS CLI and related packages. - [build-tools] Record iOS Simulator sessions through serve-sim. ([#4474](https://github.com/expo/eas-cli/pull/4474) by [@gwdp](https://github.com/gwdp)) - [eas-cli] Add browser device login with resumable commands for chat agents. ([#4502](https://github.com/expo/eas-cli/pull/4502) by [@eiiot](https://github.com/eiiot)) +- [build-tools] Upload the display rotation agent-device reports for a screenshot as device run session artifact metadata. ([#4508](https://github.com/expo/eas-cli/pull/4508) by [@szdziedzic](https://github.com/szdziedzic)) ### 🐛 Bug fixes diff --git a/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts b/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts index 94abed713f..7ab31ee117 100644 --- a/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts +++ b/packages/build-tools/src/steps/utils/__tests__/agentDeviceArtifacts.test.ts @@ -104,6 +104,39 @@ describe(listAgentDeviceArtifactsAsync, () => { }) ).rejects.toThrow('agent-device daemon does not expose artifact inventory.'); }); + + it('keeps a screenshot display rotation and drops an unknown one', async () => { + jest.mocked(fetch).mockResolvedValue( + new Response( + JSON.stringify({ + artifacts: [ + { + id: 'rotated', + artifactType: 'screenshot', + filename: 'rotated.png', + displayRotation: 'landscape-left', + }, + { + id: 'future', + artifactType: 'screenshot', + filename: 'future.png', + displayRotation: 'sideways', + }, + ], + }) + ) + ); + + const artifacts = await listAgentDeviceArtifactsAsync({ + daemonUrl: 'http://127.0.0.1:1234', + daemonToken: 'daemon-token', + }); + + expect(artifacts.map(artifact => artifact.displayRotation)).toEqual([ + 'landscape-left', + undefined, + ]); + }); }); describe(uploadAgentDeviceArtifactAsync, () => { @@ -181,6 +214,63 @@ describe(uploadAgentDeviceArtifactAsync, () => { }) ); }); + + it('uploads a screenshot display rotation as artifact metadata', async () => { + const data = Buffer.from('png-data'); + const ctx = {} as unknown as CustomBuildContext; + + jest.mocked(fetch).mockResolvedValueOnce(new Response(Readable.from([data]))); + jest + .mocked(uploadDeviceRunSessionArtifactAsync) + .mockImplementationOnce(async (_ctx, { stream }) => { + await readStreamAsync(stream); + }); + + await uploadAgentDeviceArtifactAsync(ctx, { + deviceRunSessionId: 'drs-id', + daemonUrl: 'http://127.0.0.1:1234', + daemonToken: 'daemon-token', + logger: createLoggerMock(), + artifact: { + id: 'artifact-id', + artifactType: 'screenshot', + filename: 'shot.png', + displayRotation: 'landscape-right', + }, + }); + + expect(jest.mocked(uploadDeviceRunSessionArtifactAsync)).toHaveBeenCalledWith( + ctx, + expect.objectContaining({ + kind: 'screenshot', + metadata: { displayRotation: 'landscape-right' }, + }) + ); + }); + + it('uploads no metadata when the artifact has no display rotation', async () => { + const data = Buffer.from('artifact-data'); + const ctx = {} as unknown as CustomBuildContext; + + jest.mocked(fetch).mockResolvedValueOnce(new Response(Readable.from([data]))); + jest + .mocked(uploadDeviceRunSessionArtifactAsync) + .mockImplementationOnce(async (_ctx, { stream }) => { + await readStreamAsync(stream); + }); + + await uploadAgentDeviceArtifactAsync(ctx, { + deviceRunSessionId: 'drs-id', + daemonUrl: 'http://127.0.0.1:1234', + daemonToken: 'daemon-token', + logger: createLoggerMock(), + artifact: { id: 'artifact-id', artifactType: 'screenshot', filename: 'shot.png' }, + }); + + expect(jest.mocked(uploadDeviceRunSessionArtifactAsync).mock.calls[0][1]).not.toHaveProperty( + 'metadata' + ); + }); }); describe(pollAgentDeviceArtifactsForUploadAsync, () => { diff --git a/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts b/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts index 873dd94f9c..926b9caaf1 100644 --- a/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts +++ b/packages/build-tools/src/steps/utils/agentDeviceArtifacts.ts @@ -20,6 +20,11 @@ const AgentDeviceArtifactSchema = z.object({ id: z.string(), artifactType: z.string().nullish(), filename: z.string(), + // Display rotation of a screenshot. An unknown value is dropped so it cannot block the upload. + displayRotation: z + .enum(['portrait', 'portrait-upside-down', 'landscape-left', 'landscape-right']) + .nullish() + .catch(undefined), }); const AgentDeviceArtifactsListResponseSchema = z.object({ artifacts: z.array(AgentDeviceArtifactSchema), @@ -154,6 +159,9 @@ export async function uploadAgentDeviceArtifactAsync( name: `${artifact.filename} (${artifact.id})`, filename: artifact.filename, kind: artifact.artifactType ?? undefined, + ...(artifact.displayRotation + ? { metadata: { displayRotation: artifact.displayRotation } } + : {}), size, stream: createReadStream(temporaryArtifactPath), });