Here’s a process I’m using for enabling Cypress automation tests in a hybrid PixiJS 5 / React project. PixiJS operates on HTML Canvas elements, and of course React operates on all the other DOM elements. I’m using react-pixi-fiber to bridge between the PixiJS and React worlds, which works nicely to keep everything in the same mode of thinking.
这是我用来在混合的PixiJS 5 / React项目中启用赛普拉斯自动化测试的过程。 PixiJS在HTML Canvas元素上运行,当然,React在所有其他DOM元素上运行。 我正在使用react-pixi-fiber在PixiJS和React世界之间架起桥梁,这很好地将所有内容保持在相同的思维模式下。
Cypress is great for testing web apps and has some nice features to automatically wait and retry operations. Normally, Cypress can’t see into a canvas element, which can contain an entire “app” itself, so I was hoping to open up the PixiJS side of a project to better testability. Let’s make it happen!
赛普拉斯非常适合测试Web应用程序,并且具有一些不错的功能,可以自动等待并重试操作。 通常,赛普拉斯看不到canvas元素,canvas元素本身可以包含一个完整的“应用程序”,因此我希望打开项目的PixiJS方面以提高可测试性。 一起让它成为现实!
基本思想 (Basic Idea)
Provide a mechanism where arbitrary data can be attached to any node in a PixiJS object rendering tree, and then inspected and interacted with by a Cypress test. It’ll be able to wait for a certain state to be present (or not present) in a way that works well with asynchronous code.
提供一种机制,可以将任意数据附加到PixiJS对象渲染树中的任何节点,然后通过赛普拉斯测试进行检查并与之交互。 它将能够以与异步代码良好配合的方式等待某个状态出现(或不出现)。
公开PixiJS应用程序 (Exposing the PixiJS Application)
The first step is to save a reference to the PixiJS Application object onto the window
object so that Cypress can get to the good stuff while it’s running. This happens as the result of a ref
callback (note that I’m using React Hooks).
第一步是将对PixiJS Application对象的引用保存到window
对象上,以使Cypress可以在运行时获得有用的东西。 这是由于ref
回调的结果(请注意,我正在使用React Hooks)。
I’m using TypeScript. Things should work in a JavaScript project by removing types and the like. Speaking of TypeScript, it can be useful to define types for the object we attach to and read from window
to avoid some uses of any
. For example, the declaration below will prevent errors above when it says that is not a thing.
我正在使用TypeScript 。 事情应该在JavaScript项目中通过删除类型等起作用。 说到TypeScript,为我们附加到window
并从window
读取的对象定义类型可能会很有用,以避免使用any
。 例如,下面的声明如果说不是事实,则可以防止上面的错误。
The exposed PIXI.Application object includes a whole tree of objects that correspond to the structure of the PixiJS rendering — primarily, Containers and DisplayObjects of various types. PixiJS Containers have children that may be other Containers themselves, or types of DisplayObjects, that we can traverse.
公开的PIXI.Application对象包括一整套与PixiJS呈现结构相对应的对象树-主要是各种类型的Containers和DisplayObjects。 PixiJS容器的子级可以是其他容器本身,也可以是我们可以遍历的DisplayObject类型。
将测试元数据添加到PixiJS节点 (Adding Test Metadata to PixiJS Nodes)
You can use a hyphen in a prop name to identify it as a custom attribute that will be passed through to the underlying object without getting prop errors. I decided to set my arbitrary test metadata on a pixi-data
object. Here’s how you can indicate that a particular react-pixi-fiber component is a cat:
您可以在属性名称中使用连字符将其标识为自定义属性,该属性将传递给基础对象而不会出现属性错误。 我决定在pixi-data
对象上设置任意测试元pixi-data
。 您可以通过以下方法指示特定的react-pixi-fiber组件是猫:
In these pixi-data
objects, type
is required. We have enough information above to determine whether the cat is rendered (technically, if this node exists in the PixiJS rendering tree) given the current app state.
在这些pixi-data
对象中,必须type
。 上面我们有足够的信息来确定是否在给定当前应用状态的情况下渲染了猫(从技术上讲,如果该节点存在于PixiJS渲染树中)。
If the app renders more than one cat, or you want to test more aspects of the cat, you can add arbitrary fields to pixi-data
:
如果应用程序渲染了一只以上的猫,或者您想测试猫的更多方面,则可以向pixi-data
添加任意字段:
While this doesn’t change our ability to simply test if the cat exists, we have more options now. Is Meowsers Wowsers alive or dead? Is there a cat owned by Schrödinger?
虽然这并没有改变我们简单测试猫是否存在的能力,但我们现在有更多选择。 Meowsers Wowsers活着还是死了? 薛定ding有只猫吗?
创建赛普拉斯助手功能 (Creating Cypress Helper Functions)
Let’s start making our Cypress functions to tie this all together. Somewhere in the cypress/support
files, start with:
让我们开始制作赛普拉斯功能,将其联系在一起。 在cypress/support
文件中的某处,从以下位置开始:
To avoid TypeScript errors with accessing win.pixiApp
, you can duplicate the declare global
block from earlier, or set up a reference to those window typings so they will be available here.
为了避免在访问win.pixiApp
出现TypeScript错误,您可以复制之前的declare global
块,或者设置对这些窗口类型的引用,以便在此处使用它们。
Anyway, first we define the structure of the pixi-data
object that will optionally exist on any PIXI.Container
or PIXI.DisplayObject
node. Then, a Cypress-chainable function that will get the PixiJS stage
object from window.pixiApp
object we added in the first bit of code, or blow up if it doesn’t exist at the time of calling. This is the “root” of the tree of objects in the render tree.
无论如何,首先我们定义pixi-data
对象的结构,该对象将可选地存在于任何PIXI.Container
或PIXI.DisplayObject
节点上。 然后,一个Cypress可链接函数将从我们在代码的第一位中添加的window.pixiApp
对象中获取PixiJS stage
对象,或者在调用时不存在时爆炸。 这是渲染树中对象树的“根”。
Next, a function that determines if a given set of required data matches a given PixiJS node:
接下来,一个函数确定给定的一组必需数据是否匹配给定的PixiJS节点:
I added a bit of nuance here that makes things more convenient. If pixi-data
is set on the given object, we will look at each bit of required data, and see if it exists in our custompixi-data
object or the PixiJS Container
or DisplayObject
itself. The main benefit I see here is asserting on rendered text. If you use a react-pixi-fiber
Text
component, for example <Text text='wheee' pixi-data={{ type: 'fun-times' }}/>
we can avoid duplicating the text to assert into the pixi-data
object, and instead will allow the code above to match in both “layers of the onion”. Just be aware of potential naming collisions, but I’m not worried about it, but you can make this more strict. See “Asserting on PixiJS node properties” below for more info.
我在这里添加了一些细微差别,使事情变得更加方便。 如果pixi-data
被设置在给定的对象,我们将看看需要的数据的每一位,看看它在我们的习惯中存在pixi-data
对象或 PixiJS Container
或DisplayObject
本身。 我在这里看到的主要好处是对渲染的文本进行断言。 如果您使用react-pixi-fiber
Text
组件,例如<Text text='wheee' pixi-data={{ type: 'fun-times' }}/>
我们可以避免将要复制的文本复制到pixi-data
对象,而是允许上面的代码在两个“洋葱层”中匹配。 只是要注意潜在的命名冲突,但我并不担心,但是您可以使其更加严格。 有关更多信息,请参见下面的“在PixiJS节点属性上声明” 。
Next is a function that uses pixiObjectMatches()
from above and will walk down the PixiJS tree until it finds the object (if any) that matches our requirement:
接下来是一个从上方使用pixiObjectMatches()
的函数,它将沿着PixiJS树向下走直到找到符合我们要求的对象(如果有):
If the current PixiJS node matches, it returns, and otherwise will recursively go into any children
and retry until it finds a match.
如果当前PixiJS节点匹配,则返回,否则会递归进入任何children
和重试,直到它找到一个匹配。
Because my project has asynchronous behavior driven by a server, my tests will be flaky unless I wait until the desired conditions exist, rather than failing at the first try. The functions below require cypress-wait-until so make sure that is installed.
因为我的项目具有由服务器驱动的异步行为,所以除非我等到期望的条件存在,否则我的测试会很不稳定,而不是第一次尝试失败。 以下功能需要cypress-wait-until,因此请确保已安装。
The only difference between these is that waitForNoPixiObject()
has a not (!
) in front of findPixiObject()
. Call waitForPixiObject()
to wait for, and return, the PixiJS node that matches the provided data. It will retry within the timeout period until it succeeds, or the test will fail. The same is true for waitForNoPixiObject()
, but it will retry until no node matches the provided data.
两者之间的唯一区别是, waitForNoPixiObject()
)在findPixiObject()
前面有一个not( !
findPixiObject()
。 调用waitForPixiObject()
以等待并返回与提供的数据匹配的PixiJS节点。 它将在超时时间内重试,直到成功,否则测试将失败。 对于waitForNoPixiObject()
,但是它将重试,直到没有节点匹配提供的数据。
That’s basically it! With the functions above, “wait for” is synonymous with “assert existence”. If you want to make a clearer distinction between waiting for (and returning) a node, and simply doing an assertion, you can add wrapper functions, or custom Cypress commands to that effect. The naming might give clearer intent:
基本上就是这样! 通过以上功能,“等待”与“声明存在”同义。 如果要在等待(和返回)节点与仅执行断言之间进行更清晰的区分,可以添加包装函数或自定义的Cypress命令来达到这种效果。 命名可能会给出更明确的意图:
例子 (Examples)
- Assert that no “cat” is rendered. Note that if a “cat” exists initially, it will retry until none is found, so if we are waiting for an asynchronous effect that will remove the cat, this should work as intended: 断言没有渲染“猫”。 请注意,如果最初存在“ cat”,它将重试直到找不到任何猫,因此,如果我们正在等待将删除cat的异步效果,则此操作应按预期进行:
assertNoPixiObjectExists({ type: 'cat' })
assertNoPixiObjectExists({ type: 'cat' })
- Assert that a cat named “Meowsers Wowsers” is rendered, again with the possibility of waiting until this is the case. If only cat(s) with other names are rendered, this assertion will fail: 断言渲染了一只名为“ Meowsers Wowsers”的猫,同样有可能等到这种情况。 如果仅渲染具有其他名称的猫,则此断言将失败:
assertPixiObjectExists({ type: 'cat', name: 'Meowsers Wowsers' })
assertPixiObjectExists({ type: 'cat', name: 'Meowsers Wowsers' })
- Assert that Schrödinger’s cat, if it exists, is not observed to be dead: 断言没有发现薛定ding的猫死了:
assertNoPixiObjectExists({ type: 'cat', owner: 'Schrödinger', isAlive: false })
assertNoPixiObjectExists({ type: 'cat', owner: 'Schrödinger', isAlive: false })
Notice how changing the amount of provided data corresponds to how specific we want the match (either positive or negative) to be.
请注意,更改提供的数据量如何对应于我们希望匹配(正数或负数)的具体程度。
声明PixiJS节点属性 (Asserting on PixiJS node properties)
As mentioned above, the matcher will look at fields in both our custom pixi-data
object as well as the PixiJS node object that it is attached to. This allows us to do an assertion such as:
如上所述,匹配器将查看自定义pixi-data
对象以及附加到其的PixiJS节点对象中的字段。 这使我们可以执行如下断言:
assertPixiObjectExists({ type: 'username-display', _text: 'ZenBlender' })
assertPixiObjectExists({ type: 'username-display', _text: 'ZenBlender' })
This assertion will find a PixiJS node that is driven by a react-pixi-fiber
Text
component like <Text text='ZenBlender' pixi-data={{ type: 'username-display' }}/>
. Notice that we aren’t defining _text
in the pixi-data
object, but our matcher will check its parent, where the text
prop translates to an object attribute of _text
. You can likely assert on other attributes that are populated by PixiJS.
这个断言将找到一个由react-pixi-fiber
Text
组件驱动的PixiJS节点,例如<Text text='ZenBlender' pixi-data={{ type: 'username-display' }}/>
。 请注意,我们没有在pixi-data
对象中定义_text
,但是我们的匹配器将检查其父对象,在text
道具将转换为_text
的对象属性。 您可能会声明由PixiJS填充的其他属性。
局限性 (Limitations)
This is a simple example that can surely be expanded upon. It isn’t able to return a count or an array of matching objects, or report on differences that prevented matches from being made, or to define any sort of parent / child relationships that should signify a match.
这是一个可以肯定扩展的简单示例。 它无法返回计数或匹配对象的数组,也无法报告阻止进行匹配的差异,也无法定义应表示匹配的任何父/子关系。
目前为止就这样了! (That’s all for now!)
I hope someone finds this useful!
我希望有人觉得这有用!
翻译自: https://medium.com/@zenblender/functional-testing-of-a-hybrid-pixijs-react-app-281ed5ea04b3