improve: graphql query builder test (#7618)

This commit is contained in:
Pooja
2026-04-01 17:11:22 +05:30
committed by GitHub
parent 652f3cc3fe
commit 28d1ba2438
3 changed files with 177 additions and 95 deletions

View File

@@ -5,12 +5,34 @@ const setupGraphQL = async (app) => {
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
}
type Post {
id: ID!
title: String!
body: String!
author: User!
comments: [Comment!]!
}
type Comment {
id: ID!
text: String!
author: User!
}
type Company {
ceo: String
name: String
founder: String
}
input ICreate {
id: String!
}
@@ -19,12 +41,40 @@ const setupGraphQL = async (app) => {
success: Boolean
}
union SearchResult = User | Post
input CreateUserInput {
name: String!
email: String!
age: Int
}
input UpdateUserInput {
name: String
email: String
age: Int
}
input CreatePostInput {
title: String!
body: String!
authorId: ID!
}
type Query {
company: Company
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
search(term: String!): [SearchResult!]!
}
type Mutation {
create(payload: ICreate!): Message
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
}
`,
resolvers: {
@@ -33,12 +83,54 @@ const setupGraphQL = async (app) => {
ceo: 'Elon Musk',
name: 'SpaceX',
founder: 'Elon Musk'
})
}),
user: (_parent, args) => ({
id: args.id,
name: 'John Doe',
email: 'john@example.com',
age: 30,
posts: []
}),
users: () => [
{ id: '1', name: 'John Doe', email: 'john@example.com', age: 30, posts: [] },
{ id: '2', name: 'Jane Smith', email: 'jane@example.com', age: 25, posts: [] }
],
post: (_parent, args) => ({
id: args.id,
title: 'Test Post',
body: 'Post body',
author: { id: '1', name: 'John Doe', email: 'john@example.com', age: 30, posts: [] },
comments: []
}),
search: () => []
},
Mutation: {
create: () => ({
success: true
}),
createUser: (_parent, { input }) => ({
id: '3',
...input,
posts: []
}),
updateUser: (_parent, { id, input }) => ({
id,
name: input.name || 'John Doe',
email: input.email || 'john@example.com',
age: input.age || 30,
posts: []
}),
deleteUser: () => true,
createPost: (_parent, { input }) => ({
id: '1',
title: input.title,
body: input.body,
author: { id: input.authorId, name: 'John Doe', email: 'john@example.com', age: 30, posts: [] },
comments: []
})
},
SearchResult: {
__resolveType: (obj) => (obj.email ? 'User' : 'Post')
}
}
}),

View File

@@ -5,7 +5,7 @@ info:
graphql:
method: POST
url: https://graphql.anilist.co
url: http://localhost:8081/api/graphql
body:
query: |-

View File

@@ -49,34 +49,34 @@ test.describe('GraphQL Query Builder', () => {
const introspectionItem = page.locator('[data-testid="menu-dropdown-schema-introspection"]');
await introspectionItem.waitFor({ state: 'visible' });
await introspectionItem.click();
await expect(page.getByText('GraphQL Schema loaded successfully').first()).toBeVisible({ timeout: 15000 });
await expect(page.getByText('GraphQL Schema loaded successfully').first()).toBeVisible();
await expect(qb(page).locator('.query-builder-tree')).toBeVisible();
});
await test.step('Click on "Media" field to expand it', async () => {
const mediaField = qb(page).locator('.field-node').filter({ hasText: /^Media/ }).first();
await mediaField.click();
await test.step('Click on "users" field to expand it', async () => {
const usersField = qb(page).locator('.field-node').filter({ hasText: /^users/ }).first();
await usersField.click();
await expect(qb(page).locator('.section-header').filter({ hasText: 'ARGUMENTS' }).first()).toBeVisible();
});
await test.step('Check the "Media" field checkbox', async () => {
const mediaCheckbox = qb(page).locator('.field-node').filter({ hasText: /^Media/ }).first().locator('.field-checkbox');
await mediaCheckbox.check();
await expect(mediaCheckbox).toBeChecked();
await test.step('Check the "users" field checkbox', async () => {
const usersCheckbox = qb(page).locator('.field-node').filter({ hasText: /^users/ }).first().locator('.field-checkbox');
await usersCheckbox.check();
await expect(usersCheckbox).toBeChecked();
});
await test.step('Check child fields: id, description, bannerImage', async () => {
await test.step('Check child fields: id, name, email', async () => {
const fieldsSection = qb(page).locator('.query-builder-tree');
const idField = fieldsSection.locator('.field-node').filter({ hasText: /^id\b/ }).first();
const idField = fieldsSection.locator('.field-node').filter({ hasText: /^id/ }).first();
await idField.locator('.field-checkbox').check();
const descField = fieldsSection.locator('.field-node').filter({ hasText: /^description/ }).first();
await descField.locator('.field-checkbox').check();
const nameField = fieldsSection.locator('.field-node').filter({ hasText: /^name/ }).first();
await nameField.locator('.field-checkbox').check();
const bannerField = fieldsSection.locator('.field-node').filter({ hasText: /^bannerImage/ }).first();
await bannerField.locator('.field-checkbox').check();
const emailField = fieldsSection.locator('.field-node').filter({ hasText: /^email/ }).first();
await emailField.locator('.field-checkbox').check();
});
await test.step('Verify query is generated in the editor', async () => {
@@ -84,30 +84,25 @@ test.describe('GraphQL Query Builder', () => {
await expect.poll(() => getQueryEditorContent(page)).toContain('id');
const editorContent = await getQueryEditorContent(page);
expect(editorContent).toContain('id');
expect(editorContent).toContain('description');
expect(editorContent).toContain('bannerImage');
expect(editorContent).toContain('name');
expect(editorContent).toContain('email');
});
});
test('Enable argument and set value', async ({ pageWithUserData: page }) => {
await test.step('Expand "Character" field to show arguments', async () => {
const characterField = qb(page).locator('.field-node').filter({ hasText: /^Character/ }).first();
await characterField.click();
await test.step('Expand "user" field to show arguments', async () => {
const userField = qb(page).locator('.field-node').filter({ hasText: /^user\b/ }).first();
await userField.click();
await expect(qb(page).locator('.section-header').filter({ hasText: 'ARGUMENTS' }).first()).toBeVisible();
});
await test.step('Check the "Character" field', async () => {
const characterCheckbox = qb(page)
.locator('.field-node')
.filter({ hasText: /^Character/ })
.first()
.locator('.field-checkbox');
await characterCheckbox.check();
await test.step('Check the "user" field', async () => {
const userCheckbox = qb(page).locator('.field-node').filter({ hasText: /^user\b/ }).first().locator('.field-checkbox');
await userCheckbox.check();
});
await test.step('Enable the "id" argument and set a value', async () => {
const argRow = qb(page).locator('.arg-row').filter({ has: page.locator('.arg-name', { hasText: /^id$/ }) }).first();
await expect(argRow).toBeVisible();
const argRow = qb(page).locator('.arg-row').filter({ hasText: /^id/ }).first();
const argCheckbox = argRow.locator('.field-checkbox');
await argCheckbox.check();
await expect(argCheckbox).toBeChecked();
@@ -117,78 +112,75 @@ test.describe('GraphQL Query Builder', () => {
await argInput.fill('123');
});
await test.step('Check child field "gender"', async () => {
const genderField = qb(page).locator('.field-node').filter({ hasText: /^gender/ }).first();
await genderField.locator('.field-checkbox').check();
await test.step('Check child field "name"', async () => {
const nameField = qb(page).locator('.field-node').filter({ hasText: /^name/ }).first();
await nameField.locator('.field-checkbox').check();
});
await test.step('Verify generated query contains the argument', async () => {
await expect.poll(() => getQueryEditorContent(page)).toContain('$id');
const editorContent = await getQueryEditorContent(page);
expect(editorContent).toContain('gender');
expect(editorContent).toContain('name');
expect(editorContent).toContain('$id');
});
await test.step('Verify variables pane contains the argument value', async () => {
const variablesContent = await getVariablesEditorContent(page);
expect(variablesContent).toContain('"id"');
expect(variablesContent).toContain('123');
expect(variablesContent).toContain('"123"');
});
});
test('Expand nested object types', async ({ pageWithUserData: page }) => {
await test.step('Expand "Staff" field', async () => {
const staffField = qb(page).locator('.field-node').filter({ hasText: /^Staff/ }).first();
await staffField.click();
await test.step('Expand "post" field', async () => {
const postField = qb(page).locator('.field-node').filter({ hasText: /^post/ }).first();
await postField.click();
});
await test.step('Check "Staff" and expand "name" nested field', async () => {
const staffCheckbox = qb(page).locator('.field-node').filter({ hasText: /^Staff/ }).first().locator('.field-checkbox');
await staffCheckbox.check();
await test.step('Check "post" and expand "author" nested field', async () => {
const postCheckbox = qb(page).locator('.field-node').filter({ hasText: /^post/ }).first().locator('.field-checkbox');
await postCheckbox.check();
const descField = qb(page).locator('.field-node').filter({ hasText: /^description/ }).first();
await descField.locator('.field-checkbox').check();
const titleField = qb(page).locator('.field-node').filter({ hasText: /^title/ }).first();
await titleField.locator('.field-checkbox').check();
const nameField = qb(page).locator('.field-node').filter({ hasText: /^name/ }).first();
await nameField.click();
const authorField = qb(page).locator('.field-node').filter({ hasText: /^author/ }).first();
await authorField.click();
});
await test.step('Select nested name fields', async () => {
const nameCheckbox = qb(page).locator('.field-node').filter({ hasText: /^name/ }).first().locator('.field-checkbox');
await nameCheckbox.check();
await test.step('Select nested author fields', async () => {
const authorCheckbox = qb(page).locator('.field-node').filter({ hasText: /^author/ }).first().locator('.field-checkbox');
await authorCheckbox.check();
const firstField = qb(page).locator('.field-node').filter({ hasText: /^first/ }).first();
await firstField.locator('.field-checkbox').check();
const nameFields = qb(page).locator('.field-node').filter({ hasText: /^name/ });
const authorNameField = nameFields.nth(1);
await authorNameField.locator('.field-checkbox').check();
});
await test.step('Verify nested query structure in editor', async () => {
await expect.poll(() => getQueryEditorContent(page)).toContain('Staff');
await expect.poll(() => getQueryEditorContent(page)).toContain('post');
const editorContent = await getQueryEditorContent(page);
expect(editorContent).toContain('description');
expect(editorContent).toContain('title');
expect(editorContent).toContain('author');
expect(editorContent).toContain('name');
expect(editorContent).toContain('first');
});
});
test('Removing a field in code editor unchecks it in query builder', async ({ pageWithUserData: page }) => {
await test.step('Ensure "Media" is expanded with child fields id, description, bannerImage checked', async () => {
const mediaField = qb(page).locator('.field-node').filter({ hasText: /^Media/ }).first();
const mediaChildrenVisible = await qb(page)
.locator('.field-node')
.filter({ hasText: /^bannerImage/ })
.first()
.isVisible();
if (!mediaChildrenVisible) {
await mediaField.click();
await test.step('Ensure "users" is expanded with child fields id, name, email checked', async () => {
const usersField = qb(page).locator('.field-node').filter({ hasText: /^users/ }).first();
const usersChildrenVisible = await qb(page).locator('.field-node').filter({ hasText: /^email/ }).first().isVisible();
if (!usersChildrenVisible) {
await usersField.click();
}
const mediaCheckbox = mediaField.locator('.field-checkbox');
if (!(await mediaCheckbox.isChecked())) {
await mediaCheckbox.check();
const usersCheckbox = usersField.locator('.field-checkbox');
if (!(await usersCheckbox.isChecked())) {
await usersCheckbox.check();
}
const fieldsSection = qb(page).locator('.query-builder-tree');
for (const fieldName of ['id\\b', 'description', 'bannerImage']) {
for (const fieldName of ['id', 'name', 'email']) {
const field = fieldsSection.locator('.field-node').filter({ hasText: new RegExp(`^${fieldName}`) }).first();
const checkbox = field.locator('.field-checkbox');
if (!(await checkbox.isChecked())) {
@@ -196,16 +188,15 @@ test.describe('GraphQL Query Builder', () => {
}
}
await expect.poll(() => getQueryEditorContent(page)).toContain('bannerImage');
// Wait for the Tree→Editor generation debounce (150ms) to complete
await expect.poll(() => getQueryEditorContent(page)).toContain('email');
await page.waitForTimeout(200);
});
await test.step('Remove "bannerImage" field from the code editor', async () => {
await test.step('Remove "email" field from the code editor', async () => {
const content = await getQueryEditorContent(page);
const updatedContent = content
.split('\n')
.filter((line: string) => !line.trim().startsWith('bannerImage'))
.filter((line: string) => !line.trim().startsWith('email'))
.join('\n');
// Set content directly via CodeMirror
@@ -216,45 +207,44 @@ test.describe('GraphQL Query Builder', () => {
}, updatedContent);
});
await test.step('Verify "bannerImage" checkbox is unchecked in query builder', async () => {
await test.step('Verify "email" checkbox is unchecked in query builder', async () => {
const fieldsSection = qb(page).locator('.query-builder-tree');
const bannerCheckbox = fieldsSection
const emailCheckbox = fieldsSection
.locator('.field-node')
.filter({ hasText: /^bannerImage/ })
.filter({ hasText: /^email/ })
.first()
.locator('.field-checkbox');
await expect(bannerCheckbox).not.toBeChecked();
await expect(emailCheckbox).not.toBeChecked();
});
await test.step('Verify "id" and "description" are still checked', async () => {
await test.step('Verify "id" and "name" are still checked', async () => {
const fieldsSection = qb(page).locator('.query-builder-tree');
const idCheckbox = fieldsSection.locator('.field-node').filter({ hasText: /^id\b/ }).first().locator('.field-checkbox');
const idCheckbox = fieldsSection.locator('.field-node').filter({ hasText: /^id/ }).first().locator('.field-checkbox');
await expect(idCheckbox).toBeChecked();
const descCheckbox = fieldsSection
const nameCheckbox = fieldsSection
.locator('.field-node')
.filter({ hasText: /^description/ })
.filter({ hasText: /^name/ })
.first()
.locator('.field-checkbox');
await expect(descCheckbox).toBeChecked();
await expect(nameCheckbox).toBeChecked();
});
});
test('Changing variable value in variables editor updates argument in query builder', async ({
pageWithUserData: page
}) => {
await test.step('Set up "Character" field with "id" argument via query builder', async () => {
const characterField = qb(page).locator('.field-node').filter({ hasText: /^Character/ }).first();
await characterField.click();
await test.step('Set up "user" field with "id" argument via query builder', async () => {
const userField = qb(page).locator('.field-node').filter({ hasText: /^user\b/ }).first();
await expect(qb(page).locator('.section-header').filter({ hasText: 'ARGUMENTS' }).first()).toBeVisible();
const characterCheckbox = characterField.locator('.field-checkbox');
if (!(await characterCheckbox.isChecked())) {
await characterCheckbox.check();
const userCheckbox = userField.locator('.field-checkbox');
if (!(await userCheckbox.isChecked())) {
await userCheckbox.check();
}
const argRow = qb(page).locator('.arg-row').filter({ has: page.locator('.arg-name', { hasText: /^id$/ }) }).first();
const argRow = qb(page).locator('.arg-row').filter({ hasText: /^id/ }).first();
const argCheckbox = argRow.locator('.field-checkbox');
if (!(await argCheckbox.isChecked())) {
await argCheckbox.check();
@@ -262,19 +252,19 @@ test.describe('GraphQL Query Builder', () => {
const argInput = argRow.locator('input[type="text"]');
await argInput.fill('100');
const genderField = qb(page).locator('.field-node').filter({ hasText: /^gender/ }).first();
const genderCheckbox = genderField.locator('.field-checkbox');
if (!(await genderCheckbox.isChecked())) {
await genderCheckbox.check();
const nameField = qb(page).locator('.field-node').filter({ hasText: /^name/ }).first();
const nameCheckbox = nameField.locator('.field-checkbox');
if (!(await nameCheckbox.isChecked())) {
await nameCheckbox.check();
}
await expect.poll(() => getQueryEditorContent(page)).toContain('$id');
await expect.poll(() => getVariablesEditorContent(page)).toContain('100');
await expect.poll(() => getVariablesEditorContent(page)).toContain('"100"');
});
await test.step('Change the variable value in the variables editor', async () => {
const variablesContent = await getVariablesEditorContent(page);
const updatedVariables = variablesContent.replace('100', '999');
const updatedVariables = variablesContent.replace('"100"', '"999"');
// Set content directly via CodeMirror
await ensureVariablesPaneOpen(page);
@@ -286,7 +276,7 @@ test.describe('GraphQL Query Builder', () => {
});
await test.step('Verify the argument value is updated in query builder', async () => {
const argRow = qb(page).locator('.arg-row').filter({ has: page.locator('.arg-name', { hasText: /^id$/ }) }).first();
const argRow = qb(page).locator('.arg-row').filter({ hasText: /^id/ }).first();
const argInput = argRow.locator('input[type="text"]');
await expect(argInput).toHaveValue('999');
});