如何在赛普拉斯中别名特定的GraphQL请求?

时间:2018-12-17 11:49:57

标签: graphql cypress

在赛普拉斯,您可以well-documented为特定的网络请求添加别名,然后可以“等待”。如果您想在特定的网络请求触发并完成后在赛普拉斯中执行某项操作,这将特别有用。

以下是赛普拉斯文档中的示例:

cy.server()
cy.route('POST', '**/users').as('postUser') // ALIASING OCCURS HERE
cy.visit('/users')
cy.get('#first-name').type('Julius{enter}')
cy.wait('@postUser')

但是,由于我在应用程序中使用GraphQL,因此别名不再是一件容易的事。这是因为所有GraphQL查询共享一个端点/graphql

尽管无法仅使用URL端点来区分不同的graphQL查询,但 可以使用operationName来区分graphQL查询(请参见下图)。

enter image description here

仔细阅读了文档,似乎没有一种方法可以使用请求正文中的operationName为graphQL端点创建别名。我还将响应头中的operationName(黄色箭头)作为自定义属性返回;但是,我也没有设法找到一种使用它来别名特定graphQL查询的方法。

失败的方法1::该方法尝试使用图片中显示的紫色箭头。

cy.server();
cy.route({
    method: 'POST',
    url: '/graphql',
    onResponse(reqObj) {
        if (reqObj.request.body.operationName === 'editIpo') {
            cy.wrap('editIpo').as('graphqlEditIpo');
        }
    },
});
cy.wait('@graphqlEditIpo');

此方法无效,因为graphqlEditIpo别名是在运行时注册的,因此,我收到的错误如下。

  

CypressError:cy.wait()找不到“ @graphqlEditIpo”的注册别名。可用的别名为:“ ipoInitial,graphql”。

失败的方法2::该方法尝试使用图片中显示的黄色箭头。

cy.server();
cy.route({
    method: 'POST',
    url: '/graphql',
    headers: {
        'operation-name': 'editIpo',
    },
}).as('graphql');
cy.wait('graphql');

此方法不起作用,因为cy.route的options对象中的headers属性实际上旨在接受根据docs的存根路由的响应标头。在这里,我试图用它来识别我的特定graphQL查询,这显然行不通。

哪个使我想到了一个问题:如何在赛普拉斯中为特定的graphQL查询/突变设置别名?我错过了什么吗?

9 个答案:

答案 0 :(得分:3)

这就是您要寻找的(赛普拉斯5.6.0中的新增功能):

cy.route2('POST', '/graphql', (req) => {
  if (req.body.includes('operationName')) {
    req.alias = 'gqlMutation'
  }
})

// assert that a matching request has been made
cy.wait('@gqlMutation')

文档: https://docs.cypress.io/api/commands/route2.html#Waiting-on-a-request

我希望这会有所帮助!

答案 1 :(得分:3)

6.0.0中引入的intercept API通过请求处理程序功能支持此功能。我在代码中这样使用它:

cy.intercept('POST', '/graphql', req => {
  if (req.body.operationName === 'queryName') {
    req.alias = 'queryName';
  } else if (req.body.operationName === 'mutationName') {
    req.alias = 'mutationName';
  } else if (...) {
    ...
  }
});

其中queryNamemutationName是您的GQL操作的名称。您可以为每个要别名的请求添加一个附加条件。然后,您可以像这样等待它们:

// Wait on single request
cy.wait('@mutationName');

// Wait on multiple requests. 
// Useful if several requests are fired at once, for example on page load. 
cy.wait(['@queryName, @mutationName',...]);

文档在此处具有类似的示例:https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-requests

答案 2 :(得分:1)

如果“等待”而不是“混淆”本身是主要目的,那么到目前为止,最简单的方法是通过别名通用graphql请求,然后对“等待”以新创建的别名为目标,直到找到所需的特定graphql操作。 例如

Cypress.Commands.add('waitFor', operationName => {
  cy.wait('@graphqlRequest').then(({ request }) => {
    if (request.body.operationName !== operationName) {
      return cy.waitFor(operationName)
    }
  })
})

这当然有其注意事项,并且在您的情况下可能有效也可能无效。但这对我们有用。

我希望赛普拉斯将来能够以一种不太骇人听闻的方式实现这一目标。

PS。我想赞扬我从中获得灵感的地方,但似乎在网络空间中消失了。

答案 3 :(得分:1)

由于我遇到了同样的问题,但没有找到解决该问题的真正方法,因此我组合了不同的选项,并创建了一种解决方法来解决我的问题。希望这也可以帮助其他人。

我并不是真的“等待”请求发生,但是我根据**/graphql网址捕获了所有请求,并匹配了请求中的operationName。在比赛中,将以数据作为参数执行功能。在此功能中,可以定义测试。

graphQLResponse.js

export const onGraphQLResponse = (resolvers, args) => {
    resolvers.forEach((n) => {
        const operationName = Object.keys(n).shift();
        const nextFn = n[operationName];

        if (args.request.body.operationName === operationName) {
            handleGraphQLResponse(nextFn)(args.response)(operationName);
        }
    });
};

const handleGraphQLResponse = (next) => {
    return (response) => {

        const responseBody = Cypress._.get(response, "body");

        return async (alias) => {
            await Cypress.Blob.blobToBase64String(responseBody)
                .then((blobResponse) => atob(blobResponse))
                .then((jsonString) => JSON.parse(jsonString))
                .then((jsonResponse) => {
                    Cypress.log({
                        name: "wait blob",
                        displayName: `Wait ${alias}`,
                        consoleProps: () => {
                            return jsonResponse.data;
                        }
                    }).end();

                    return jsonResponse.data;
                })
                .then((data) => {
                    next(data);
                });
        };
    };
};

在测试文件中

使用对象为键的操作绑定绑定数组,其值为解析函数。

import { onGraphQLResponse } from "./util/graphQLResponse";

describe("Foo and Bar", function() {
    it("Should be able to test GraphQL response data", () => {
        cy.server();

        cy.route({
            method: "POST",
            url: "**/graphql",
            onResponse: onGraphQLResponse.bind(null, [
                {"some operationName": testResponse},
                {"some other operationName": testOtherResponse}
            ])
        }).as("graphql");

        cy.visit("");

        function testResponse(result) {
            const foo = result.foo;
            expect(foo.label).to.equal("Foo label");
        }

        function testOtherResponse(result) {
            const bar = result.bar;
            expect(bar.label).to.equal("Bar label");
        }
    });
}

积分

使用了glebbahmutov.com中的blob命令

答案 4 :(得分:1)

这对我有用!

Cypress.Commands.add('waitForGraph', operationName => {
  const GRAPH_URL = '/api/v2/graph/';
  cy.route('POST', GRAPH_URL).as("graphqlRequest");
  //This will capture every request
  cy.wait('@graphqlRequest').then(({ request }) => {
    // If the captured request doesn't match the operation name of your query
    // it will wait again for the next one until it gets matched.
    if (request.body.operationName !== operationName) {
      return cy.waitForGraph(operationName)
    }
  })
})

请记住,要使用唯一的名称来编写查询,因为操作名称依赖于此。

答案 5 :(得分:1)

我使用了其中的一些代码示例,但必须对其稍作更改以将onRequest参数添加到cy.route并添加日期。现在(可以添加任何自动增量器,对此可以打开其他解决方案)以允许多个在同一测试中调用相同的GraphQL操作名称。感谢您为我指出正确的方向!

Cypress.Commands.add('waitForGraph', (operationName) => {
  const now = Date.now()
  let operationNameFromRequest
  cy.route({
    method: 'POST',
    url: '**graphql',
    onRequest: (xhr) => {
      operationNameFromRequest = xhr.request.body.operationName
    },
  }).as(`graphqlRequest${now}`)

  //This will capture every request
  cy.wait(`@graphqlRequest${now}`).then(({ xhr }) => {
    // If the captured request doesn't match the operation name of your query
    // it will wait again for the next one until it gets matched.
    if (operationNameFromRequest !== operationName) {
      return cy.waitForGraph(operationName)
    }
  })
})

使用:

cy.waitForGraph('QueryAllOrganizations').then((xhr) => { ...

答案 6 :(得分:1)

this method was suggested的其他地方。

一旦您使用migrate to Cypress v5.x and make use of the new route (route2)方法,一切都会变得容易一些。

答案 7 :(得分:0)

这是我设法区分每个GraphQL请求的方式。我们使用 cypress-cucumber-preprocessor ,因此我们在 / cypress / integration / common / 中有一个 common.js 文件,我们可以将其称为<在任何功能文件之前调用的strong> before 和 beforeEach 挂钩。

我在这里尝试了解决方案,但是无法提供稳定的方法,因为在我们的应用程序中,为某些操作同时触发了许多GraphQL请求。

我最终将每个GraphQL请求存储在一个名为 graphql_accumulator 的全局对象中,并为每次发生添加了时间戳。

使用cypress命令应该来管理单个请求更加容易。

common.js:

beforeEach(() => {
  for (const query in graphql_accumulator) {
    delete graphql_accumulator[query];
  }

  cy.server();
  cy.route({
    method: 'POST',
    url: '**/graphql',
    onResponse(xhr) {
      const queryName = xhr.requestBody.get('query').trim().split(/[({ ]/)[1];
      if (!(queryName in graphql_accumulator)) graphql_accumulator[queryName] = [];
      graphql_accumulator[queryName].push({timeStamp: nowStamp('HHmmssSS'), data: xhr.responseBody.data})
    }
  });
});

我必须从FormData中提取queryName,因为我们在请求标头中还没有键 operationName ,但这是您要使用此键的地方。

commands.js

Cypress.Commands.add('waitGraphQL', {prevSubject:false}, (queryName) => {
  Cypress.log({
    displayName: 'wait gql',
    consoleProps() {
      return {
        'graphQL Accumulator': graphql_accumulator
      }
    }
  });
  const timeMark = nowStamp('HHmmssSS');
  cy.wrap(graphql_accumulator, {log:false}).should('have.property', queryName)
    .and("satisfy", responses => responses.some(response => response['timeStamp'] >= timeMark));
});

让cypress通过在 /cypress/support/index.js 中添加以下设置来管理GraphQL请求也很重要:

Cypress.on('window:before:load', win => {
  // unfilters incoming GraphQL requests in cypress so we can see them in the UI
  // and track them with cy.server; cy.route
  win.fetch = null;
  win.Blob = null; // Avoid Blob format for GraphQL responses
});

我这样使用它:

cy.waitGraphQL('QueryChannelConfigs');
cy.get(button_edit_market).click();

cy.waitGraphQL 将等待最新的目标请求,该目标请求将在调用后存储。

希望这会有所帮助。

答案 8 :(得分:0)

我们的用例在一页上涉及多个GraphQL调用。我们必须使用上面答复的修改版本:

Cypress.Commands.add('createGql', operation => {
    cy.route({
        method: 'POST',
        url: '**/graphql',
    }).as(operation);
});

Cypress.Commands.add('waitForGql', (operation, nextOperation) => {
    cy.wait(`@${operation}`).then(({ request }) => {
        if (request.body.operationName !== operation) {
            return cy.waitForGql(operation);
        }

        cy.route({
            method: 'POST',
            url: '**/graphql',
        }).as(nextOperation || 'gqlRequest');
    });
});

问题在于,所有GraphQL请求都共享相同的URL,因此一旦为一个GraphQL查询创建了cy.route(),赛普拉斯就会将所有以下GraphQL查询与之匹配。匹配之后,我们将cy.route()设置为默认标签gqlRequest或下一个查询。

我们的测试:

cy.get(someSelector)
  .should('be.visible')
  .type(someText)
  .createGql('gqlOperation1')
  .waitForGql('gqlOperation1', 'gqlOperation2') // Create next cy.route() for the next query, or it won't match
  .get(someSelector2)
  .should('be.visible')
  .click();

cy.waitForGql('gqlOperation2')
  .get(someSelector3)
  .should('be.visible')
  .click();