Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions e2e/pom/protos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { uiGoto } from '@e2e/utils/ui';
import { expect, type Page } from '@playwright/test';

const locator = {
getProtoNavBtn: (page: Page) =>
page.getByRole('link', { name: 'Protos' }),
getAddProtoBtn: (page: Page) =>
page.getByRole('button', { name: 'Add Proto' }),
getAddBtn: (page: Page) =>
page.getByRole('button', { name: 'Add', exact: true }),
};

const assert = {
isIndexPage: async (page: Page) => {
await expect(page).toHaveURL((url) => url.pathname.endsWith('/protos'));
const title = page.getByRole('heading', { name: 'Protos' });
await expect(title).toBeVisible();
},
isAddPage: async (page: Page) => {
await expect(page).toHaveURL((url) =>
url.pathname.endsWith('/protos/add')
);
const title = page.getByRole('heading', { name: 'Add Proto' });
await expect(title).toBeVisible();
},
isDetailPage: async (page: Page) => {
await expect(page).toHaveURL((url) =>
url.pathname.includes('/protos/detail')
);
const title = page.getByRole('heading', { name: 'Proto Detail' });
await expect(title).toBeVisible();
},
};

const goto = {
toIndex: (page: Page) => uiGoto(page, '/protos'),
toAdd: (page: Page) => uiGoto(page, '/protos/add'),
};

export const protosPom = {
...locator,
...assert,
...goto,
};
142 changes: 142 additions & 0 deletions e2e/tests/protos.crud-all-fields.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { protosPom } from '@e2e/pom/protos';
import { e2eReq } from '@e2e/utils/req';
import { test } from '@e2e/utils/test';
import { expect } from '@playwright/test';

import { API_PROTOS } from '@/config/constant';
import type { APISIXType } from '@/types/schema/apisix';

const protoContent = `syntax = "proto3";
package test;

message TestMessage {
string name = 1;
int32 age = 2;
string email = 3;
}`;

let createdProtoId: string;

test.describe('CRUD proto with all fields', () => {
test.describe.configure({ mode: 'serial' });

test.afterAll(async () => {
// cleanup: delete the proto
if (createdProtoId) {
await e2eReq.delete(`${API_PROTOS}/${createdProtoId}`).catch(() => {
// ignore error if proto doesn't exist
});
}
});

test('should create a proto with all fields', async ({ page }) => {
await test.step('navigate to add proto page', async () => {
await protosPom.toAdd(page);
await protosPom.isAddPage(page);
});

await test.step('fill in all fields', async () => {
// Fill Content (ID is auto-generated, proto only has content field)
await page.getByLabel('Content').fill(protoContent);
});

await test.step('submit the form', async () => {
await page.getByRole('button', { name: 'Add', exact: true }).click();

// Should redirect to list page after successful creation
await protosPom.isIndexPage(page);
});

await test.step('verify proto was created via API', async () => {
// Get the list of protos to find the created one
const protos = await e2eReq
.get<unknown, APISIXType['RespProtoList']>(API_PROTOS)
.then((v) => v.data);

// Find the proto with our content (search for exact package name)
const createdProto = protos.list.find((p) =>
p.value.content?.includes('package test;')
);
expect(createdProto).toBeDefined();
expect(createdProto?.value.id).toBeDefined();
// eslint-disable-next-line playwright/no-conditional-in-test
createdProtoId = createdProto?.value.id || '';

// Verify content matches
expect(createdProto?.value.content).toBe(protoContent);
});
});

test('should read/view the proto details', async () => {
await test.step('verify proto can be retrieved via API', async () => {
const proto = await e2eReq
.get<unknown, APISIXType['RespProtoDetail']>(
`${API_PROTOS}/${createdProtoId}`
)
.then((v) => v.data);

expect(proto.value?.id).toBe(createdProtoId);
expect(proto.value?.content).toBe(protoContent);
expect(proto.value?.create_time).toBeDefined();
expect(proto.value?.update_time).toBeDefined();
});
});

test('should update the proto with new values', async () => {
const updatedContent = `syntax = "proto3";
package test_updated;

message UpdatedTestMessage {
string updated_name = 1;
int32 updated_age = 2;
string email = 3;
bool is_active = 4;
}`;

await test.step('update proto via API', async () => {
await e2eReq.put(`${API_PROTOS}/${createdProtoId}`, {
content: updatedContent,
});
});

await test.step('verify proto was updated via API', async () => {
const proto = await e2eReq
.get<unknown, APISIXType['RespProtoDetail']>(
`${API_PROTOS}/${createdProtoId}`
)
.then((v) => v.data);

expect(proto.value?.id).toBe(createdProtoId);
expect(proto.value?.content).toBe(updatedContent);
});
});

test('should delete the proto', async () => {
await test.step('delete proto via API', async () => {
await e2eReq.delete(`${API_PROTOS}/${createdProtoId}`);
});

await test.step('verify proto was deleted via API', async () => {
await expect(async () => {
await e2eReq.get(`${API_PROTOS}/${createdProtoId}`);
}).rejects.toThrow();
});
});
});
140 changes: 140 additions & 0 deletions e2e/tests/protos.crud-required-fields.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { protosPom } from '@e2e/pom/protos';
import { e2eReq } from '@e2e/utils/req';
import { test } from '@e2e/utils/test';
import { expect } from '@playwright/test';

import { API_PROTOS } from '@/config/constant';
import type { APISIXType } from '@/types/schema/apisix';

const protoContent = `syntax = "proto3";
package test_required;

message TestMessageRequired {
string name = 1;
int32 age = 2;
}`;

let createdProtoId: string;

test.describe('CRUD proto with required fields only', () => {
test.describe.configure({ mode: 'serial' });

test.afterAll(async () => {
// cleanup: delete the proto
if (createdProtoId) {
await e2eReq.delete(`${API_PROTOS}/${createdProtoId}`).catch(() => {
// ignore error if proto doesn't exist
});
}
});

test('should create a proto with required fields', async ({ page }) => {
await test.step('navigate to add proto page', async () => {
await protosPom.toAdd(page);
await protosPom.isAddPage(page);
});

await test.step('fill in required fields', async () => {
// Fill Content (ID is auto-generated)
await page.getByLabel('Content').fill(protoContent);
});

await test.step('submit the form', async () => {
await page.getByRole('button', { name: 'Add', exact: true }).click();

// Should redirect to list page after successful creation
await protosPom.isIndexPage(page);
});

await test.step('verify proto was created via API', async () => {
// Get the list of protos to find the created one
const protos = await e2eReq
.get<unknown, APISIXType['RespProtoList']>(API_PROTOS)
.then((v) => v.data);

// Find the proto with our content
const createdProto = protos.list.find((p) =>
p.value.content?.includes('package test_required')
);
expect(createdProto).toBeDefined();
expect(createdProto?.value.id).toBeDefined();
// eslint-disable-next-line playwright/no-conditional-in-test
createdProtoId = createdProto?.value.id || '';

// Verify content matches
expect(createdProto?.value.content).toBe(protoContent);
});
});

test('should read/view the proto details', async () => {
await test.step('verify proto can be retrieved via API', async () => {
const proto = await e2eReq
.get<unknown, APISIXType['RespProtoDetail']>(
`${API_PROTOS}/${createdProtoId}`
)
.then((v) => v.data);

expect(proto.value?.id).toBe(createdProtoId);
expect(proto.value?.content).toBe(protoContent);
expect(proto.value?.create_time).toBeDefined();
expect(proto.value?.update_time).toBeDefined();
});
});

test('should update the proto', async () => {
const updatedContent = `syntax = "proto3";
package test_updated;

message UpdatedTestMessage {
string updated_name = 1;
int32 updated_age = 2;
string email = 3;
}`;

await test.step('update proto via API', async () => {
await e2eReq.put(`${API_PROTOS}/${createdProtoId}`, {
content: updatedContent,
});
});

await test.step('verify proto was updated via API', async () => {
const proto = await e2eReq
.get<unknown, APISIXType['RespProtoDetail']>(
`${API_PROTOS}/${createdProtoId}`
)
.then((v) => v.data);

expect(proto.value?.id).toBe(createdProtoId);
expect(proto.value?.content).toBe(updatedContent);
});
});

test('should delete the proto', async () => {
await test.step('delete proto via API', async () => {
await e2eReq.delete(`${API_PROTOS}/${createdProtoId}`);
});

await test.step('verify proto was deleted via API', async () => {
await expect(async () => {
await e2eReq.get(`${API_PROTOS}/${createdProtoId}`);
}).rejects.toThrow();
});
});
});
Loading
Loading