Cypress: File's instanceof File is false. Two windows have different File classes.

Created on 19 Nov 2017  路  2Comments  路  Source: cypress-io/cypress

  • Operating System: Windows 7
  • Cypress Version: 1.0.3
  • Browser Version: Chrome 62

Is this a Feature or Bug?

Bug

Current behavior:

File class is not shared with window's children.

Desired behavior:

File class is somehow shared.

How to reproduce:

  1. Write test and create a file in it.
  2. Use created file in app code (instanceof File)

Test code:

describe('Create new File', function() {
  it('should has same File class', function() {
    const file = new File(/* arguments */);
    console.log(file instanceof File); // true

    // test runs in new window with different File
    doSomethingWithFile(file); // false
  });
});
// App code
const doSomethingWithFile = file => console.log(file instanceof File);

Additional Info (images, stack traces, etc)

Where did I encounter a problem: https://github.com/jaydenseric/extract-files/blob/10236ac78548a3f9c64d148aa79e1444e8dff285/src/index.js#L33

Object.prototype.toString.call(node[key]) === "[object File]" // works

SO topic: https://stackoverflow.com/questions/30040096/a-file-object-in-chrome-passed-between-windows-is-no-longer-an-instanceof-file

Temporary hack

// vendor/extract-files/index.js

// https://github.com/jaydenseric/extract-files/blob/master/src/index.js
// https://github.com/cypress-io/cypress/issues/933
/**
 * Checks a node is an enumerable object.
 * @param {*} node - A node to check.
 * @returns {Boolean} Is the node an enumerable object.
 */
export const isObject = node => typeof node === 'object' && node !== null

/**
 * A file extraction.
 * @typedef {Object} ExtractedFile
 * @property {String} path - Original location in the object tree.
 * @property {String} file - The actual file.
 */

/**
 * Reversibly extracts files from an object tree.
 * @param {object} tree - An object tree to extract files from.
 * @param {string} [treePath=''] - Optional tree path to prefix file paths.
 * @returns {ExtractedFile[]} Extracted files.
 */
export function extractFiles(tree, treePath = '') {
  const files = []
  const recurse = (node, nodePath) => {
    // Iterate enumerable properties of the node
    Object.keys(node).forEach(key => {
      // Skip non-object
      if (!isObject(node[key])) return

      const path = `${nodePath}${key}`

      if (
        // Node is a File
      (typeof File !== 'undefined' &&
        Object.prototype.toString.call(node[key]) === '[object File]') ||
      // Node is a ReactNativeFile
      node[key] instanceof ReactNativeFile
      ) {
        // Extract the file and it's object tree path
        files.push({ path, file: node[key] })

        // Delete the file. Array items must be deleted without reindexing to
        // allow repopulation in a reverse operation.
        delete node[key]

        // No further checks or recursion
        return
      }

      if (typeof FileList !== 'undefined' && node[key] instanceof FileList)
      // Convert read-only FileList to an array for manipulation
        node[key] = Array.from(node[key])

      // Recurse into child node
      recurse(node[key], `${path}.`)
    })
  }

  if (isObject(tree))
  // Recurse object tree
    recurse(
      tree,
      // If a tree path was provided, append a dot
      treePath === '' ? treePath : `${treePath}.`
    )

  return files
}

/**
 * A React Native FormData file object.
 * @see {@link https://github.com/facebook/react-native/blob/v0.45.1/Libraries/Network/FormData.js#L34}
 * @typedef {Object} ReactNativeFileObject
 * @property {String} uri - File system path.
 * @property {String} [type] - File content type.
 * @property {String} [name] - File name.
 */

/**
 * A React Native file.
 */
export class ReactNativeFile {
  /**
   * Constructs a new file.
   * @param {ReactNativeFileObject} file
   * @example
   * const file = new ReactNativeFile({
   *  uri: uriFromCameraRoll,
   *  type: 'image/jpeg',
   *  name: 'photo.jpg'
   * })
   */
  constructor({ uri, type, name }) {
    this.uri = uri
    this.type = type
    this.name = name
  }

  /**
   * Creates an array of file instances.
   * @param {ReactNativeFileObject[]} files
   * @example
   * const files = ReactNativeFile.list([{
   *   uri: uriFromCameraRoll1,
   *   type: 'image/jpeg',
   *   name: 'photo-1.jpg'
   * }, {
   *   uri: uriFromCameraRoll2,
   *   type: 'image/jpeg',
   *   name: 'photo-2.jpg'
   * }])
   */
  static list = files => files.map(file => new ReactNativeFile(file))
}
// package.json (this works only for yarn)

"resolutions": {
  "apollo-upload-client/extract-files": "file:./vendor/extract-files"
}
// webpack.config.js

module: {
  rules: [
    {
      test: /\.(js|jsx|mjs)$/,
      include: [
        path.resolve(__dirname, '../node_modules/extract-files'),
        // ...
      ],
    }
  ]
}

Most helpful comment

This should be fairly simple to fix: you simply need to instantiate Files from your application's window, and not from the test spec window.

// nope
// this is using the global window in the test spec
new File() 

cy.window().then((win) => {
  // yup
  // this is using the File constructor from the application window
  new win.File()
})

All 2 comments

This should be fairly simple to fix: you simply need to instantiate Files from your application's window, and not from the test spec window.

// nope
// this is using the global window in the test spec
new File() 

cy.window().then((win) => {
  // yup
  // this is using the File constructor from the application window
  new win.File()
})

This isn't related to Windows OS. It affects all usecases where File/Blob/Filelist instances are created in cypress test files. I use MacOS, for example.

I've added comment to recently updated issue: https://github.com/cypress-io/cypress/issues/170#issuecomment-442843559

Was this page helpful?
0 / 5 - 0 ratings