diff --git a/test/fixtures/monitors.sb3 b/test/fixtures/monitors.sb3
new file mode 100644
index 000000000..1343243dd
Binary files /dev/null and b/test/fixtures/monitors.sb3 differ
diff --git a/test/integration/monitors.js b/test/integration/monitors_sb2.js
similarity index 100%
rename from test/integration/monitors.js
rename to test/integration/monitors_sb2.js
diff --git a/test/integration/monitors_sb3.js b/test/integration/monitors_sb3.js
new file mode 100644
index 000000000..60a280e98
--- /dev/null
+++ b/test/integration/monitors_sb3.js
@@ -0,0 +1,264 @@
+const path = require('path');
+const test = require('tap').test;
+const makeTestStorage = require('../fixtures/make-test-storage');
+const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer;
+const VirtualMachine = require('../../src/index');
+const Variable = require('../../src/engine/variable');
+
+const projectUri = path.resolve(__dirname, '../fixtures/monitors.sb3');
+const project = readFileToBuffer(projectUri);
+
+test('importing sb2 project with monitors', t => {
+    const vm = new VirtualMachine();
+    vm.attachStorage(makeTestStorage());
+
+    // Evaluate playground data and exit
+    vm.on('playgroundData', e => {
+        const threads = JSON.parse(e.threads);
+        // All monitors should create threads that finish during the step and
+        // are revoved from runtime.threads.
+        t.equal(threads.length, 0);
+        t.equal(vm.runtime._lastStepDoneThreads.length, 17);
+        // There should be one additional hidden monitor that is in the monitorState but
+        // does not start a thread.
+        t.equal(vm.runtime._monitorState.size, 18);
+
+        const stage = vm.runtime.targets[0];
+        const shirtSprite = vm.runtime.targets[1];
+        const heartSprite = vm.runtime.targets[2];
+
+        // Global variable named "my variable" exists
+        let variableId = Object.keys(stage.variables).filter(k => stage.variables[k].name === 'my variable')[0];
+        let monitorRecord = vm.runtime._monitorState.get(variableId);
+        let monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId);
+        t.equal(monitorRecord.opcode, 'data_variable');
+        t.equal(monitorRecord.mode, 'default');
+        // The following few properties are imported for all monitors, just check once.
+        // sliderMin and sliderMax are currently not implemented,
+        // but should still get default values serialized and deserialized correctly
+        t.equal(monitorRecord.sliderMin, 0);
+        t.equal(monitorRecord.sliderMax, 100);
+        t.equal(monitorRecord.x, 10);
+        t.equal(monitorRecord.y, 62);
+        // Height and width are only used for list monitors and should default to 0
+        // for all other monitors
+        t.equal(monitorRecord.width, 0);
+        t.equal(monitorRecord.height, 0);
+        t.equal(monitorRecord.visible, true);
+        t.type(monitorRecord.params, 'object');
+        // The variable name should be stored in the monitor params
+        t.equal(monitorRecord.params.VARIABLE, 'my variable');
+        // Test that the monitor block and its fields were constructed correctly
+        t.equal(monitorBlock.fields.VARIABLE.value, 'my variable');
+        t.equal(monitorBlock.fields.VARIABLE.name, 'VARIABLE');
+        t.equal(monitorBlock.fields.VARIABLE.id, variableId);
+        t.equal(monitorBlock.fields.VARIABLE.variableType, Variable.SCALAR_TYPE);
+
+        // There is a global variable named 'secret_slide' which has a hidden monitor
+        variableId = Object.keys(stage.variables).filter(k => stage.variables[k].name === 'secret_slide')[0];
+        monitorRecord = vm.runtime._monitorState.get(variableId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId);
+        t.equal(monitorRecord.opcode, 'data_variable');
+        t.equal(monitorRecord.mode, 'slider');
+        t.equal(monitorRecord.visible, false);
+        t.equal(monitorRecord.sliderMin, 0);
+        t.equal(monitorRecord.sliderMax, 100);
+        t.type(monitorRecord.params, 'object');
+        t.equal(monitorRecord.params.VARIABLE, 'secret_slide');
+        // Test that the monitor block and its fields were constructed correctly
+        t.equal(monitorBlock.fields.VARIABLE.value, 'secret_slide');
+        t.equal(monitorBlock.fields.VARIABLE.name, 'VARIABLE');
+        t.equal(monitorBlock.fields.VARIABLE.id, variableId);
+        t.equal(monitorBlock.fields.VARIABLE.variableType, Variable.SCALAR_TYPE);
+
+
+        // Shirt sprite has a local list named "fashion"
+        variableId = Object.keys(shirtSprite.variables).filter(k => shirtSprite.variables[k].name === 'fashion')[0];
+        monitorRecord = vm.runtime._monitorState.get(variableId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId);
+        t.equal(monitorRecord.opcode, 'data_listcontents');
+        t.equal(monitorRecord.mode, 'list');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.height, 122);
+        t.equal(monitorRecord.width, 104);
+        t.type(monitorRecord.params, 'object');
+        t.equal(monitorRecord.params.LIST, 'fashion'); // The list name should be stored in the monitor params
+        // Test that the monitor block and its fields were constructed correctly
+        t.equal(monitorBlock.fields.LIST.value, 'fashion');
+        t.equal(monitorBlock.fields.LIST.name, 'LIST');
+        t.equal(monitorBlock.fields.LIST.id, variableId);
+        t.equal(monitorBlock.fields.LIST.variableType, Variable.LIST_TYPE);
+
+        // Shirt sprite has a local variable named "tee"
+        variableId = Object.keys(shirtSprite.variables).filter(k => shirtSprite.variables[k].name === 'tee')[0];
+        monitorRecord = vm.runtime._monitorState.get(variableId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId);
+        t.equal(monitorRecord.opcode, 'data_variable');
+        t.equal(monitorRecord.mode, 'slider');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.sliderMin, 0);
+        t.equal(monitorRecord.sliderMax, 100);
+        t.type(monitorRecord.params, 'object');
+        t.equal(monitorRecord.params.VARIABLE, 'tee');
+        // Test that the monitor block and its fields were constructed correctly
+        t.equal(monitorBlock.fields.VARIABLE.value, 'tee');
+        t.equal(monitorBlock.fields.VARIABLE.name, 'VARIABLE');
+        t.equal(monitorBlock.fields.VARIABLE.id, variableId);
+        t.equal(monitorBlock.fields.VARIABLE.variableType, Variable.SCALAR_TYPE);
+
+        // Heart sprite has a local list named "hearty"
+        variableId = Object.keys(heartSprite.variables).filter(k => heartSprite.variables[k].name === 'hearty')[0];
+        monitorRecord = vm.runtime._monitorState.get(variableId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(variableId);
+        t.equal(monitorRecord.opcode, 'data_variable');
+        t.equal(monitorRecord.mode, 'default');
+        t.equal(monitorRecord.visible, true);
+        t.type(monitorRecord.params, 'object');
+        t.equal(monitorRecord.params.VARIABLE, 'hearty'); // The variable name should be stored in the monitor params
+        // Test that the monitor block and its fields were constructed correctly
+        t.equal(monitorBlock.fields.VARIABLE.value, 'hearty');
+        t.equal(monitorBlock.fields.VARIABLE.name, 'VARIABLE');
+        t.equal(monitorBlock.fields.VARIABLE.id, variableId);
+        t.equal(monitorBlock.fields.VARIABLE.variableType, Variable.SCALAR_TYPE);
+
+        // Backdrop name monitor is visible, not sprite specific
+        // should get imported with id that references the name parameter
+        // via '_name' at the end since the 3.0 block has a dropdown.
+        let monitorId = 'backdropnumbername_name';
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorRecord.opcode, 'looks_backdropnumbername');
+        t.equal(monitorRecord.mode, 'default');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, null);
+        t.equal(monitorRecord.targetId, null);
+        // Test that the monitor block and its fields were constructed correctly
+        t.equal(monitorBlock.fields.NUMBER_NAME.value, 'name');
+
+        // Backdrop name monitor is visible, not sprite specific
+        // should get imported with id that references the name parameter
+        // via '_number' at the end since the 3.0 block has a dropdown.
+        monitorId = 'backdropnumbername_number';
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorRecord.opcode, 'looks_backdropnumbername');
+        t.equal(monitorRecord.mode, 'default');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, null);
+        t.equal(monitorRecord.targetId, null);
+        // Test that the monitor block and its fields were constructed correctly
+        t.equal(monitorBlock.fields.NUMBER_NAME.value, 'number');
+
+        // x position monitor is in large mode, specific to shirt sprite
+        monitorId = `${shirtSprite.id}_xposition`;
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorRecord.opcode, 'motion_xposition');
+        t.equal(monitorRecord.mode, 'large');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, 'Shirt-T');
+        t.equal(monitorRecord.targetId, shirtSprite.id);
+
+        // y position monitor is in large mode, specific to shirt sprite
+        monitorId = `${shirtSprite.id}_yposition`;
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorRecord.opcode, 'motion_yposition');
+        t.equal(monitorRecord.mode, 'large');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, 'Shirt-T');
+        t.equal(monitorRecord.targetId, shirtSprite.id);
+
+        // direction monitor is in large mode, specific to shirt sprite
+        monitorId = `${shirtSprite.id}_direction`;
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorRecord.opcode, 'motion_direction');
+        t.equal(monitorRecord.mode, 'large');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, 'Shirt-T');
+        t.equal(monitorRecord.targetId, shirtSprite.id);
+
+        monitorId = `${shirtSprite.id}_size`;
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorRecord.opcode, 'looks_size');
+        t.equal(monitorRecord.mode, 'large');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, 'Shirt-T');
+        t.equal(monitorRecord.targetId, shirtSprite.id);
+
+        // The monitor IDs for the sensing_current block should be unique
+        // to the parameter that is selected on the block being monitored.
+        // The paramater portion of the id should be lowercase even
+        // though the field value on the block is uppercase.
+        monitorId = 'current_date';
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        t.equal(monitorRecord.opcode, 'sensing_current');
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorBlock.fields.CURRENTMENU.value, 'DATE');
+        t.equal(monitorRecord.mode, 'default');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, null);
+        t.equal(monitorRecord.targetId, null);
+
+        monitorId = 'current_year';
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        t.equal(monitorRecord.opcode, 'sensing_current');
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorBlock.fields.CURRENTMENU.value, 'YEAR');
+        t.equal(monitorRecord.mode, 'default');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, null);
+        t.equal(monitorRecord.targetId, null);
+
+        monitorId = 'current_month';
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        t.equal(monitorRecord.opcode, 'sensing_current');
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorBlock.fields.CURRENTMENU.value, 'MONTH');
+        t.equal(monitorRecord.mode, 'default');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, null);
+        t.equal(monitorRecord.targetId, null);
+
+        // Extension Monitors
+        monitorId = 'music_getTempo';
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        t.equal(monitorRecord.opcode, 'music_getTempo');
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorRecord.mode, 'default');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, null);
+        t.equal(monitorRecord.targetId, null);
+        t.equal(vm.extensionManager.isExtensionLoaded('music'), true);
+
+        monitorId = 'ev3_getDistance';
+        monitorRecord = vm.runtime._monitorState.get(monitorId);
+        t.equal(monitorRecord.opcode, 'ev3_getDistance');
+        monitorBlock = vm.runtime.monitorBlocks.getBlock(monitorId);
+        t.equal(monitorRecord.mode, 'default');
+        t.equal(monitorRecord.visible, true);
+        t.equal(monitorRecord.spriteName, null);
+        t.equal(monitorRecord.targetId, null);
+        t.equal(vm.extensionManager.isExtensionLoaded('ev3'), true);
+
+        t.end();
+        process.nextTick(process.exit);
+    });
+
+    // Start VM, load project, and run
+    t.doesNotThrow(() => {
+        vm.start();
+        vm.clear();
+        vm.setCompatibilityMode(false);
+        vm.setTurboMode(false);
+        vm.loadProject(project).then(() => {
+            vm.greenFlag();
+            setTimeout(() => {
+                vm.getPlaygroundData();
+                vm.stopAll();
+            }, 100);
+        });
+    });
+});