diff --git a/package.json b/package.json
index 7840f355b7fdeb23cf13e4b928c19c03911921b8..e44c89a024c70922b937446d231ca6829ae5e337 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
     "eslint-config-scratch": "^5.0.0",
     "eslint-plugin-import": "^2.8.0",
     "eslint-plugin-react": "^7.5.1",
-    "file-loader": "1.1.6",
+    "file-loader": "1.1.9",
     "get-float-time-domain-data": "0.1.0",
     "get-user-media-promise": "1.1.1",
     "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder",
@@ -77,8 +77,8 @@
     "react-ga": "2.4.1",
     "react-intl": "2.4.0",
     "react-intl-redux": "0.7.0",
-    "react-modal": "3.1.13",
-    "react-redux": "5.0.6",
+    "react-modal": "3.3.1",
+    "react-redux": "5.0.7",
     "react-responsive": "4.0.3",
     "react-style-proptype": "3.2.0",
     "react-tabs": "2.2.1",
@@ -89,12 +89,12 @@
     "redux-throttle": "0.1.1",
     "rimraf": "^2.6.1",
     "scratch-audio": "0.1.0-prerelease.1516198804",
-    "scratch-blocks": "0.1.0-prerelease.1518210548",
+    "scratch-blocks": "0.1.0-prerelease.1519256473",
     "scratch-l10n": "2.0.20180108132626",
-    "scratch-paint": "0.1.0-prerelease.20180209163938",
+    "scratch-paint": "0.2.0-prerelease.20180222192821",
     "scratch-render": "0.1.0-prerelease.1516837442",
     "scratch-storage": "0.4.0",
-    "scratch-vm": "0.1.0-prerelease.1518098842-prerelease.1518098861",
+    "scratch-vm": "0.1.0-prerelease.1519653784-prerelease.1519653801",
     "selenium-webdriver": "3.6.0",
     "startaudiocontext": "1.2.1",
     "style-loader": "^0.20.0",
diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx
index 012206517b48133c965b9514c21a0926821575e7..b6970778155ca4ccd205092e593a310554e16821 100644
--- a/src/components/asset-panel/selector.jsx
+++ b/src/components/asset-panel/selector.jsx
@@ -13,6 +13,7 @@ const Selector = props => {
         items,
         selectedItemIndex,
         onDeleteClick,
+        onDuplicateClick,
         onItemClick
     } = props;
 
@@ -30,6 +31,7 @@ const Selector = props => {
                         selected={index === selectedItemIndex}
                         onClick={onItemClick}
                         onDeleteButtonClick={onDeleteClick}
+                        onDuplicateButtonClick={onDuplicateClick}
                     />
                 ))}
             </Box>
@@ -58,6 +60,7 @@ Selector.propTypes = {
         name: PropTypes.string.isRequired
     })),
     onDeleteClick: PropTypes.func,
+    onDuplicateClick: PropTypes.func,
     onItemClick: PropTypes.func.isRequired,
     selectedItemIndex: PropTypes.number.isRequired
 };
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index fe58243ba3bd4649fa7cb6ed4f146f194ef85ee8..488e705e88db5ce7cb76a4cc320446b0dab1b35b 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -35,16 +35,18 @@ const messages = defineMessages({
 
 const GUIComponent = props => {
     const {
+        activeTabIndex,
         basePath,
+        blocksTabVisible,
         children,
-        enableExtensions,
-        intl,
+        costumesTabVisible,
         feedbackFormVisible,
-        vm,
-        previewInfoVisible,
+        intl,
         onExtensionButtonClick,
-        onTabSelect,
-        tabIndex,
+        onActivateTab,
+        previewInfoVisible,
+        soundsTabVisible,
+        vm,
         ...componentProps
     } = props;
     if (children) {
@@ -87,9 +89,10 @@ const GUIComponent = props => {
                         <Tabs
                             className={tabClassNames.tabs}
                             forceRenderTabPanel={true} // eslint-disable-line react/jsx-boolean-value
+                            selectedIndex={activeTabIndex}
                             selectedTabClassName={tabClassNames.tabSelected}
                             selectedTabPanelClassName={tabClassNames.tabPanelSelected}
-                            onSelect={onTabSelect}
+                            onSelect={onActivateTab}
                         >
                             <TabList className={tabClassNames.tabList}>
                                 <Tab className={tabClassNames.tab}>Blocks</Tab>
@@ -100,7 +103,7 @@ const GUIComponent = props => {
                                 <Box className={styles.blocksWrapper}>
                                     <Blocks
                                         grow={1}
-                                        isVisible={tabIndex === 0} // Blocks tab
+                                        isVisible={blocksTabVisible}
                                         options={{
                                             media: `${basePath}static/blocks-media/`
                                         }}
@@ -109,9 +112,7 @@ const GUIComponent = props => {
                                 </Box>
                                 <Box className={styles.extensionButtonContainer}>
                                     <button
-                                        className={classNames(styles.extensionButton, {
-                                            [styles.hidden]: !enableExtensions
-                                        })}
+                                        className={styles.extensionButton}
                                         title={intl.formatMessage(messages.addExtension)}
                                         onClick={onExtensionButtonClick}
                                     >
@@ -124,10 +125,10 @@ const GUIComponent = props => {
                                 </Box>
                             </TabPanel>
                             <TabPanel className={tabClassNames.tabPanel}>
-                                {tabIndex === 1 ? <CostumeTab vm={vm} /> : null}
+                                {costumesTabVisible ? <CostumeTab vm={vm} /> : null}
                             </TabPanel>
                             <TabPanel className={tabClassNames.tabPanel}>
-                                {tabIndex === 2 ? <SoundTab vm={vm} /> : null}
+                                {soundsTabVisible ? <SoundTab vm={vm} /> : null}
                             </TabPanel>
                         </Tabs>
                     </Box>
@@ -162,15 +163,17 @@ const GUIComponent = props => {
     );
 };
 GUIComponent.propTypes = {
+    activeTabIndex: PropTypes.number,
     basePath: PropTypes.string,
+    blocksTabVisible: PropTypes.bool,
     children: PropTypes.node,
-    enableExtensions: PropTypes.bool,
+    costumesTabVisible: PropTypes.bool,
     feedbackFormVisible: PropTypes.bool,
     intl: intlShape.isRequired,
+    onActivateTab: PropTypes.func,
     onExtensionButtonClick: PropTypes.func,
-    onTabSelect: PropTypes.func,
     previewInfoVisible: PropTypes.bool,
-    tabIndex: PropTypes.number,
+    soundsTabVisible: PropTypes.bool,
     vm: PropTypes.instanceOf(VM).isRequired
 };
 GUIComponent.defaultProps = {
diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx
index 17b562120996b073e35f53e85e056385e1ec2bb6..5fb521e9cf7af1a85d2951d9efddc1c1511220f0 100644
--- a/src/components/monitor/monitor.jsx
+++ b/src/components/monitor/monitor.jsx
@@ -9,7 +9,8 @@ const categories = {
     sensing: '#5CB1D6',
     sound: '#CF63CF',
     looks: '#9966FF',
-    motion: '#4C97FF'
+    motion: '#4C97FF',
+    list: '#FC662C'
 };
 
 const MonitorComponent = props => (
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index a53b487600ce9341a1b958ad04a4806ab81ea3e7..bedbbdbade90c98fe17572495521ca28478cdef9 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -252,7 +252,10 @@ class Blocks extends React.Component {
     }
     handleCustomProceduresClose (data) {
         this.props.onRequestCloseCustomProcedures(data);
-        this.workspace.refreshToolboxSelection_();
+        const ws = this.workspace;
+        ws.refreshToolboxSelection_();
+        // @todo ensure this does not break on localization
+        ws.toolbox_.scrollToCategoryByName('My Blocks');
     }
     render () {
         /* eslint-disable no-unused-vars */
diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx
index be4c554fee26de0e9d0637266c76c9e40eed03e9..e22baebf203f86de87d305a6a0b79a58f74051e8 100644
--- a/src/containers/costume-tab.jsx
+++ b/src/containers/costume-tab.jsx
@@ -51,10 +51,21 @@ class CostumeTab extends React.Component {
         bindAll(this, [
             'handleSelectCostume',
             'handleDeleteCostume',
+            'handleDuplicateCostume',
             'handleNewCostume',
             'handleNewBlankCostume'
         ]);
-        this.state = {selectedCostumeIndex: 0};
+        const {
+            editingTarget,
+            sprites,
+            stage
+        } = props;
+        const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage;
+        if (target && target.currentCostume) {
+            this.state = {selectedCostumeIndex: target.currentCostume};
+        } else {
+            this.state = {selectedCostumeIndex: 0};
+        }
     }
     componentWillReceiveProps (nextProps) {
         const {
@@ -75,9 +86,14 @@ class CostumeTab extends React.Component {
     handleDeleteCostume (costumeIndex) {
         this.props.vm.deleteCostume(costumeIndex);
     }
+    handleDuplicateCostume (costumeIndex) {
+        this.props.vm.duplicateCostume(costumeIndex).then(() => {
+            this.setState({selectedCostumeIndex: costumeIndex + 1});
+        });
+    }
     handleNewCostume () {
         if (!this.props.vm.editingTarget) return;
-        const costumes = this.props.vm.editingTarget.sprite.costumes || [];
+        const costumes = this.props.vm.editingTarget.getCostumes() || [];
         this.setState({selectedCostumeIndex: Math.max(costumes.length - 1, 0)});
     }
     handleNewBlankCostume () {
@@ -145,6 +161,7 @@ class CostumeTab extends React.Component {
                 items={target.costumes || []}
                 selectedItemIndex={this.state.selectedCostumeIndex}
                 onDeleteClick={target.costumes.length > 1 ? this.handleDeleteCostume : null}
+                onDuplicateClick={this.handleDuplicateCostume}
                 onItemClick={this.handleSelectCostume}
             >
                 {target.costumes ?
@@ -186,7 +203,8 @@ CostumeTab.propTypes = {
         id: PropTypes.shape({
             costumes: PropTypes.arrayOf(PropTypes.shape({
                 url: PropTypes.string,
-                name: PropTypes.string.isRequired
+                name: PropTypes.string.isRequired,
+                skinId: PropTypes.number
             }))
         })
     }),
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index 539705a5808dccd5991bd8ef0bbc3dac512288b8..bae6f9684ec5e3553a2d1eeb4e32eb10da4793d0 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -2,23 +2,21 @@ import AudioEngine from 'scratch-audio';
 import PropTypes from 'prop-types';
 import React from 'react';
 import VM from 'scratch-vm';
-import bindAll from 'lodash.bindall';
 import {connect} from 'react-redux';
 
-import {openExtensionLibrary} from '../reducers/modals.js';
+import {openExtensionLibrary} from '../reducers/modals';
+import {
+    activateTab,
+    BLOCKS_TAB_INDEX,
+    COSTUMES_TAB_INDEX,
+    SOUNDS_TAB_INDEX
+} from '../reducers/editor-tab';
 
 import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
 
 import GUIComponent from '../components/gui/gui.jsx';
 
 class GUI extends React.Component {
-    constructor (props) {
-        super(props);
-        bindAll(this, [
-            'handleTabSelect'
-        ]);
-        this.state = {tabIndex: 0};
-    }
     componentDidMount () {
         this.audioEngine = new AudioEngine();
         this.props.vm.attachAudioEngine(this.audioEngine);
@@ -34,9 +32,6 @@ class GUI extends React.Component {
     componentWillUnmount () {
         this.props.vm.stopAll();
     }
-    handleTabSelect (tabIndex) {
-        this.setState({tabIndex});
-    }
     render () {
         const {
             children,
@@ -46,10 +41,7 @@ class GUI extends React.Component {
         } = this.props;
         return (
             <GUIComponent
-                enableExtensions={window.location.search.includes('extensions')}
-                tabIndex={this.state.tabIndex}
                 vm={vm}
-                onTabSelect={this.handleTabSelect}
                 {...componentProps}
             >
                 {children}
@@ -69,12 +61,17 @@ GUI.propTypes = {
 GUI.defaultProps = GUIComponent.defaultProps;
 
 const mapStateToProps = state => ({
+    activeTabIndex: state.editorTab.activeTabIndex,
+    blocksTabVisible: state.editorTab.activeTabIndex === BLOCKS_TAB_INDEX,
+    costumesTabVisible: state.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
     feedbackFormVisible: state.modals.feedbackForm,
-    previewInfoVisible: state.modals.previewInfo
+    previewInfoVisible: state.modals.previewInfo,
+    soundsTabVisible: state.editorTab.activeTabIndex === SOUNDS_TAB_INDEX
 });
 
 const mapDispatchToProps = dispatch => ({
-    onExtensionButtonClick: () => dispatch(openExtensionLibrary())
+    onExtensionButtonClick: () => dispatch(openExtensionLibrary()),
+    onActivateTab: tab => dispatch(activateTab(tab))
 });
 
 const ConnectedGUI = connect(
diff --git a/src/containers/sound-editor.jsx b/src/containers/sound-editor.jsx
index 781b2cc5fe51f9c068c46a7ae7467d0c93e9a10f..bbe032c0b67bf897fb2ecdb6edf26cdc04b5c41f 100644
--- a/src/containers/sound-editor.jsx
+++ b/src/containers/sound-editor.jsx
@@ -1,6 +1,7 @@
 import bindAll from 'lodash.bindall';
 import PropTypes from 'prop-types';
 import React from 'react';
+import WavEncoder from 'wav-encoder';
 
 import {connect} from 'react-redux';
 
@@ -9,6 +10,7 @@ import {computeChunkedRMS} from '../lib/audio/audio-util.js';
 import AudioEffects from '../lib/audio/audio-effects.js';
 import SoundEditorComponent from '../components/sound-editor/sound-editor.jsx';
 import AudioBufferPlayer from '../lib/audio/audio-buffer-player.js';
+import log from '../lib/log.js';
 
 const UNDO_STACK_SIZE = 99;
 
@@ -72,11 +74,25 @@ class SoundEditor extends React.Component {
             }
             this.undoStack.push(this.copyCurrentBuffer());
         }
+        // Encode the new sound into a wav so that it can be stored
+        let wavBuffer = null;
+        try {
+            wavBuffer = WavEncoder.encode.sync({
+                sampleRate: sampleRate,
+                channelData: [samples]
+            });
+        } catch (e) {
+            // This error state is mostly for the mock sounds used during testing.
+            // Any incorrect sound buffer trying to get interpretd as a Wav file
+            // should yield this error.
+            log.error(`Encountered error while trying to encode sound update: ${e}`);
+        }
+
         this.resetState(samples, sampleRate);
         this.props.onUpdateSoundBuffer(
             this.props.soundIndex,
-            this.audioBufferPlayer.buffer
-        );
+            this.audioBufferPlayer.buffer,
+            wavBuffer ? new Uint8Array(wavBuffer) : new Uint8Array());
     }
     handlePlay () {
         this.audioBufferPlayer.play(
diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx
index 9e30ba2edf81b5ec045de55a89fc644b44bfd340..8686ae6f90528c6ad010e40cc72965730dec6e7c 100644
--- a/src/containers/sound-tab.jsx
+++ b/src/containers/sound-tab.jsx
@@ -26,6 +26,7 @@ class SoundTab extends React.Component {
         bindAll(this, [
             'handleSelectSound',
             'handleDeleteSound',
+            'handleDuplicateSound',
             'handleNewSound'
         ]);
         this.state = {selectedSoundIndex: 0};
@@ -51,6 +52,15 @@ class SoundTab extends React.Component {
 
     handleDeleteSound (soundIndex) {
         this.props.vm.deleteSound(soundIndex);
+        if (soundIndex >= this.state.selectedSoundIndex) {
+            this.setState({selectedSoundIndex: Math.max(0, soundIndex - 1)});
+        }
+    }
+
+    handleDuplicateSound (soundIndex) {
+        this.props.vm.duplicateSound(soundIndex).then(() => {
+            this.setState({selectedSoundIndex: soundIndex + 1});
+        });
     }
 
     handleNewSound () {
@@ -113,6 +123,7 @@ class SoundTab extends React.Component {
                 }))}
                 selectedItemIndex={this.state.selectedSoundIndex}
                 onDeleteClick={this.handleDeleteSound}
+                onDuplicateClick={this.handleDuplicateSound}
                 onItemClick={this.handleSelectSound}
             >
                 {sprite.sounds && sprite.sounds[this.state.selectedSoundIndex] ? (
diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx
index d1a3e4b3fac0a29cc74fafbdd8d19dd799b2a534..44253e6cfa66644d82d4987db97c38f3edeafc77 100644
--- a/src/containers/sprite-selector-item.jsx
+++ b/src/containers/sprite-selector-item.jsx
@@ -22,7 +22,8 @@ class SpriteSelectorItem extends React.Component {
         e.preventDefault();
         this.props.onClick(this.props.id);
     }
-    handleDelete () {
+    handleDelete (e) {
+        e.stopPropagation(); // To prevent from bubbling back to handleClick
         // @todo add i18n here
         // eslint-disable-next-line no-alert
         if (window.confirm('Are you sure you want to delete this?')) {
diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx
index 5095f86dd074cc221c7561f12886a6ecbc7b0ed9..c386112b3858cbfbc8bc4040c84bff2ec88ac88b 100644
--- a/src/containers/stage.jsx
+++ b/src/containers/stage.jsx
@@ -31,6 +31,7 @@ class Stage extends React.Component {
             'onMouseDown',
             'onStartDrag',
             'onStopDrag',
+            'onWheel',
             'updateRect',
             'questionListener',
             'setCanvas'
@@ -98,6 +99,7 @@ class Stage extends React.Component {
         document.addEventListener('touchend', this.onMouseUp);
         canvas.addEventListener('mousedown', this.onMouseDown);
         canvas.addEventListener('touchstart', this.onMouseDown);
+        canvas.addEventListener('wheel', this.onWheel);
     }
     detachMouseEvents (canvas) {
         document.removeEventListener('mousemove', this.onMouseMove);
@@ -106,6 +108,7 @@ class Stage extends React.Component {
         document.removeEventListener('touchend', this.onMouseUp);
         canvas.removeEventListener('mousedown', this.onMouseDown);
         canvas.removeEventListener('touchstart', this.onMouseDown);
+        canvas.removeEventListener('wheel', this.onWheel);
     }
     attachRectEvents () {
         window.addEventListener('resize', this.updateRect);
@@ -232,6 +235,13 @@ class Stage extends React.Component {
             this.setState({colorInfo: null});
         }
     }
+    onWheel (e) {
+        const data = {
+            deltaX: e.deltaX,
+            deltaY: e.deltaY
+        };
+        this.props.vm.postIOData('mouseWheel', data);
+    }
     cancelMouseDownTimeout () {
         if (this.state.mouseDownTimeoutId !== null) {
             clearTimeout(this.state.mouseDownTimeoutId);
diff --git a/src/lib/blocks.js b/src/lib/blocks.js
index af15abc8760295c9b61674c1ecf68ffaca19c13a..06c9a459c02bcc3005712d4e503a4667f60b2dfb 100644
--- a/src/lib/blocks.js
+++ b/src/lib/blocks.js
@@ -36,15 +36,15 @@ export default function (vm) {
     };
 
     const costumesMenu = function () {
-        if (vm.editingTarget && vm.editingTarget.sprite.costumes.length > 0) {
-            return vm.editingTarget.sprite.costumes.map(costume => [costume.name, costume.name]);
+        if (vm.editingTarget && vm.editingTarget.getCostumes().length > 0) {
+            return vm.editingTarget.getCostumes().map(costume => [costume.name, costume.name]);
         }
         return [['', '']];
     };
 
     const backdropsMenu = function () {
-        if (vm.runtime.targets[0] && vm.runtime.targets[0].sprite.costumes.length > 0) {
-            return vm.runtime.targets[0].sprite.costumes.map(costume => [costume.name, costume.name])
+        if (vm.runtime.targets[0] && vm.runtime.targets[0].getCostumes().length > 0) {
+            return vm.runtime.targets[0].getCostumes().map(costume => [costume.name, costume.name])
                 .concat([['next backdrop', 'next backdrop'], ['previous backdrop', 'previous backdrop']]);
         }
         return [['', '']];
diff --git a/src/lib/opcode-labels.js b/src/lib/opcode-labels.js
index c4fa07bcfd998f81962c1a50388675797dc49029..e877a2a51290d29f33534b0c5e8cccf589bae312 100644
--- a/src/lib/opcode-labels.js
+++ b/src/lib/opcode-labels.js
@@ -37,10 +37,10 @@ const opcodeMap = {
         labelFn: params => params.VARIABLE
     },
     data_listcontents: {
-        category: 'data',
+        category: 'list',
         labelFn: params => params.LIST
     },
-    
+
     // Sound
     sound_volume: {
         category: 'sound',
diff --git a/src/reducers/editor-tab.js b/src/reducers/editor-tab.js
new file mode 100644
index 0000000000000000000000000000000000000000..21b3534e29fb2700a4a2469886445d4e2da0b557
--- /dev/null
+++ b/src/reducers/editor-tab.js
@@ -0,0 +1,37 @@
+const ACTIVATE_TAB = 'scratch-gui/navigation/ACTIVATE_TAB';
+
+// Constants use numbers to make it easier to work with react-tabs
+const BLOCKS_TAB_INDEX = 0;
+const COSTUMES_TAB_INDEX = 1;
+const SOUNDS_TAB_INDEX = 2;
+
+const initialState = {
+    activeTabIndex: BLOCKS_TAB_INDEX
+};
+
+const reducer = function (state, action) {
+    if (typeof state === 'undefined') state = initialState;
+    switch (action.type) {
+    case ACTIVATE_TAB:
+        return Object.assign({}, state, {
+            activeTabIndex: action.activeTabIndex
+        });
+    default:
+        return state;
+    }
+};
+
+const activateTab = function (tab) {
+    return {
+        type: ACTIVATE_TAB,
+        activeTabIndex: tab
+    };
+};
+
+export {
+    reducer as default,
+    activateTab,
+    BLOCKS_TAB_INDEX,
+    COSTUMES_TAB_INDEX,
+    SOUNDS_TAB_INDEX
+};
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index 49b9a0120394a6e1992074c0d4c459c1ec7693ae..d89a195c9dc2d485082337e5a0961f18e2e63ddd 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -2,6 +2,7 @@ import {combineReducers} from 'redux';
 import colorPickerReducer from './color-picker';
 import customProceduresReducer from './custom-procedures';
 import blockDragReducer from './block-drag';
+import editorTabReducer from './editor-tab';
 import hoveredTargetReducer from './hovered-target';
 import intlReducer from './intl';
 import modalReducer from './modals';
@@ -17,6 +18,7 @@ export default combineReducers({
     blockDrag: blockDragReducer,
     colorPicker: colorPickerReducer,
     customProcedures: customProceduresReducer,
+    editorTab: editorTabReducer,
     hoveredTarget: hoveredTargetReducer,
     intl: intlReducer,
     stageSize: stageSizeReducer,
diff --git a/test/helpers/selenium-helper.js b/test/helpers/selenium-helper.js
index c8ead90725e7197bf1ab966e94d05a7145ae88e5..b63ea2bf66ac321ae9dc68f79906af68c21d9d16 100644
--- a/test/helpers/selenium-helper.js
+++ b/test/helpers/selenium-helper.js
@@ -25,6 +25,7 @@ class SeleniumHelper {
         // List of useful xpath scopes for finding elements
         return {
             blocksTab: "*[@id='react-tabs-1']",
+            costumesTab: "*[@id='react-tabs-3']",
             modal: '*[@class="ReactModalPortal"]',
             reportedValue: '*[@class="blocklyDropDownContent"]',
             soundsTab: "*[@id='react-tabs-5']",
diff --git a/test/integration/costumes.test.js b/test/integration/costumes.test.js
index 98f31629a591b97ff5024b3c94db2dc605100f11..f07c3d0f309cd27ecdddc7c7b2eaa769f2c8448a 100644
--- a/test/integration/costumes.test.js
+++ b/test/integration/costumes.test.js
@@ -7,7 +7,9 @@ const {
     findByXpath,
     getDriver,
     getLogs,
-    loadUri
+    loadUri,
+    rightClickText,
+    scope
 } = new SeleniumHelper();
 
 const uri = path.resolve(__dirname, '../../build/index.html');
@@ -36,6 +38,22 @@ describe('Working with costumes', () => {
         await expect(logs).toEqual([]);
     });
 
+    test('Duplicating a costume', async () => {
+        await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
+        await clickText('Costumes');
+
+        await rightClickText('costume1', scope.costumesTab);
+        await clickText('duplicate', scope.costumesTab);
+        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for duplication to finish
+
+        // Make sure the duplicated costume is named correctly.
+        await clickText('costume3', scope.costumesTab);
+
+        const logs = await getLogs();
+        await expect(logs).toEqual([]);
+    });
+
     test('Adding a backdrop', async () => {
         await loadUri(uri);
         await clickXpath('//button[@title="tryit"]');
diff --git a/test/integration/sounds.test.js b/test/integration/sounds.test.js
index 244ebb9b454d7b5d88b17b243db47fc9e4ad44ef..e3154868417ae9194c3e0c4eb736ca3a4f70cdc9 100644
--- a/test/integration/sounds.test.js
+++ b/test/integration/sounds.test.js
@@ -63,6 +63,22 @@ describe('Working with sounds', () => {
         await expect(logs).toEqual([]);
     });
 
+    test('Duplicating a sound', async () => {
+        await loadUri(uri);
+        await clickXpath('//button[@title="tryit"]');
+        await clickText('Sounds');
+
+        await rightClickText('Meow', scope.soundsTab);
+        await clickText('duplicate', scope.soundsTab);
+        await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for error
+
+        // Make sure the duplicated sound is named correctly.
+        await clickText('Meow2', scope.soundsTab);
+
+        const logs = await getLogs();
+        await expect(logs).toEqual([]);
+    });
+
     // Regression test for gui issue #1320
     test('Switching sprites with different numbers of sounds', async () => {
         await loadUri(uri);
diff --git a/webpack.config.js b/webpack.config.js
index c92f72ea718775322dbd7b29aca36dedbbcb5734..389cf8abd6db12e1deb277d680469a688d518cb6 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -111,10 +111,6 @@ module.exports = {
             from: 'node_modules/scratch-blocks/media',
             to: 'static/blocks-media'
         }]),
-        new CopyWebpackPlugin([{
-            from: 'node_modules/scratch-vm/dist/node/assets',
-            to: 'static/extension-assets'
-        }]),
         new CopyWebpackPlugin([{
             from: 'extensions/**',
             to: 'static',